c14dbc828dd4 — Leonard Ritter tip 5 years ago
* check-in legacy python projects
813 files changed, 246335 insertions(+), 0 deletions(-)

A => autobind-cffi/.hg_archival.txt
A => autobind-cffi/.hgignore
A => autobind-cffi/LICENSE
A => autobind-cffi/README
A => autobind-cffi/autobind/__init__.py
A => autobind-cffi/setup.py
A => glue.py/.hg_archival.txt
A => glue.py/.hgignore
A => glue.py/.hgtags
A => glue.py/LICENSE
A => glue.py/README.text
A => glue.py/glue.py
A => glue.py/setup.py
A => liminal_legacy/.hg_archival.txt
A => liminal_legacy/.hgignore
A => liminal_legacy/LICENSE
A => liminal_legacy/TODO
A => liminal_legacy/doc/Makefile
A => liminal_legacy/doc/articles/linkdemo.py
A => liminal_legacy/doc/articles/split-scheme.txt
A => liminal_legacy/doc/make.bat
A => liminal_legacy/doc/source/_static/__donotdelete__
A => liminal_legacy/doc/source/_templates/__donotdelete__
A => liminal_legacy/doc/source/conf.py
A => liminal_legacy/doc/source/index.txt
A => liminal_legacy/doc/source/reference.txt
A => liminal_legacy/liminal/__init__.py
A => liminal_legacy/liminal/assets/fonts/DejaVuSans.ttf
A => liminal_legacy/liminal/assets/fonts/SourceSansPro-Black.ttf
A => liminal_legacy/liminal/assets/fonts/SourceSansPro-BlackItalic.ttf
A => liminal_legacy/liminal/assets/fonts/SourceSansPro-Bold.ttf
A => liminal_legacy/liminal/assets/fonts/SourceSansPro-BoldItalic.ttf
A => liminal_legacy/liminal/assets/fonts/SourceSansPro-ExtraLight.ttf
A => liminal_legacy/liminal/assets/fonts/SourceSansPro-ExtraLightItalic.ttf
A => liminal_legacy/liminal/assets/fonts/SourceSansPro-Italic.ttf
A => liminal_legacy/liminal/assets/fonts/SourceSansPro-Light.ttf
A => liminal_legacy/liminal/assets/fonts/SourceSansPro-LightItalic.ttf
A => liminal_legacy/liminal/assets/fonts/SourceSansPro-Regular.ttf
A => liminal_legacy/liminal/assets/fonts/SourceSansPro-Semibold.ttf
A => liminal_legacy/liminal/assets/fonts/SourceSansPro-SemiboldItalic.ttf
A => liminal_legacy/liminal/assets/fonts/UbuntuMono-R.ttf
A => liminal_legacy/liminal/assets/fonts/raleway_thin.ttf
A => liminal_legacy/liminal/assets/liminal/a4epro2.beigefont
A => liminal_legacy/liminal/assets/liminal/button_normal.png
A => liminal_legacy/liminal/assets/liminal/button_pressed.png
A => liminal_legacy/liminal/assets/liminal/cursor.png
A => liminal_legacy/liminal/assets/liminal/dosis.beigefont
A => liminal_legacy/liminal/assets/liminal/nevis.beigefont
A => liminal_legacy/liminal/assets/liminal/window.png
A => liminal_legacy/liminal/assets/liminal/window_bg.png
A => liminal_legacy/liminal/assets/liminal/window_closing.png
A => liminal_legacy/liminal/assets/shaders/flat/simple.glsl
A => liminal_legacy/liminal/assets/shaders/font/font.glsl
A => liminal_legacy/liminal/assets/shaders/font/font_gshader.fs
A => liminal_legacy/liminal/assets/shaders/font/font_gshader.vs
A => liminal_legacy/liminal/assets/shaders/font/simple.glsl
A => liminal_legacy/liminal/assets/shaders/g/gbuffer_attr.glsl
A => liminal_legacy/liminal/assets/shaders/g/gshader.glsl
A => liminal_legacy/liminal/assets/shaders/g/gshader_frag.glsl
A => liminal_legacy/liminal/assets/shaders/g/gshader_sdf.glsl
A => liminal_legacy/liminal/assets/shaders/g/gshader_vars.glsl
A => liminal_legacy/liminal/assets/shaders/g/gshader_vert.glsl
A => liminal_legacy/liminal/assets/shaders/g/lpv/propagate.glsl
A => liminal_legacy/liminal/assets/shaders/g/ppfx/dof-mblur.glsl
A => liminal_legacy/liminal/assets/shaders/g/ppfx/gshader.glsl
A => liminal_legacy/liminal/assets/shaders/g/ppfx/gshader_frag.glsl
A => liminal_legacy/liminal/assets/shaders/g/ppfx/light_direct.glsl
A => liminal_legacy/liminal/assets/shaders/g/ppfx/light_proj.glsl
A => liminal_legacy/liminal/assets/shaders/g/ppfx/light_shgrid.glsl
A => liminal_legacy/liminal/assets/shaders/g/ppfx/ssao.glsl
A => liminal_legacy/liminal/assets/shaders/g/ppfx/ssgi.glsl
A => liminal_legacy/liminal/assets/shaders/g/ppfx/view_ray.glsl
A => liminal_legacy/liminal/assets/shaders/g/skybox.glsl
A => liminal_legacy/liminal/assets/shaders/lib/blur.glsl
A => liminal_legacy/liminal/assets/shaders/lib/brdf.glsl
A => liminal_legacy/liminal/assets/shaders/lib/evsm.glsl
A => liminal_legacy/liminal/assets/shaders/lib/fog.glsl
A => liminal_legacy/liminal/assets/shaders/lib/hammersley.glsl
A => liminal_legacy/liminal/assets/shaders/lib/hslhsv.glsl
A => liminal_legacy/liminal/assets/shaders/lib/ibl_sample.glsl
A => liminal_legacy/liminal/assets/shaders/lib/mapping.glsl
A => liminal_legacy/liminal/assets/shaders/lib/math.glsl
A => liminal_legacy/liminal/assets/shaders/lib/normal_codec.glsl
A => liminal_legacy/liminal/assets/shaders/lib/perturb.glsl
A => liminal_legacy/liminal/assets/shaders/lib/sdf_math.glsl
A => liminal_legacy/liminal/assets/shaders/lib/sh.glsl
A => liminal_legacy/liminal/assets/shaders/lib/snoise3.glsl
A => liminal_legacy/liminal/assets/shaders/lib/srgb.glsl
A => liminal_legacy/liminal/assets/shaders/lib/ycbcr.glsl
A => liminal_legacy/liminal/assets/shaders/lib/ycocg.glsl
A => liminal_legacy/liminal/assets/shaders/nm/nm_frag.glsl
A => liminal_legacy/liminal/assets/shaders/nm/nm_vars.glsl
A => liminal_legacy/liminal/assets/shaders/nm/nm_vert.glsl
A => liminal_legacy/liminal/assets/shaders/pom/pom_frag.glsl
A => liminal_legacy/liminal/assets/shaders/pom/pom_vars.glsl
A => liminal_legacy/liminal/assets/shaders/pom/pom_vert.glsl
A => liminal_legacy/liminal/assets/shaders/ppfx/blit.glsl
A => liminal_legacy/liminal/assets/shaders/ppfx/blit_layers.glsl
A => liminal_legacy/liminal/assets/shaders/ppfx/bloom_mixdown.glsl
A => liminal_legacy/liminal/assets/shaders/ppfx/blur.glsl
A => liminal_legacy/liminal/assets/shaders/ppfx/copy.glsl
A => liminal_legacy/liminal/assets/shaders/ppfx/copy_luma_ds2.glsl
A => liminal_legacy/liminal/assets/shaders/ppfx/debug_color.glsl
A => liminal_legacy/liminal/assets/shaders/ppfx/debug_layer_color.glsl
A => liminal_legacy/liminal/assets/shaders/ppfx/debug_layer_depth.glsl
A => liminal_legacy/liminal/assets/shaders/ppfx/debug_material.glsl
A => liminal_legacy/liminal/assets/shaders/ppfx/debug_normal.glsl
A => liminal_legacy/liminal/assets/shaders/ppfx/debug_velocity.glsl
A => liminal_legacy/liminal/assets/shaders/ppfx/debug_ycocg.glsl
A => liminal_legacy/liminal/assets/shaders/ppfx/derivative_map.glsl
A => liminal_legacy/liminal/assets/shaders/ppfx/fxaa3_11.glsl
A => liminal_legacy/liminal/assets/shaders/ppfx/luma.glsl
A => liminal_legacy/liminal/assets/shaders/ppfx/oculus_rift.glsl
A => liminal_legacy/liminal/assets/shaders/ppfx/ppfx_debug_depth.fs
A => liminal_legacy/liminal/assets/shaders/ppfx/ppfx_hdr_clip.fs
A => liminal_legacy/liminal/assets/shaders/ppfx/ppfx_max.fs
A => liminal_legacy/liminal/assets/shaders/ppfx/tonemap.glsl
A => liminal_legacy/liminal/assets/shaders/profiler/simple.glsl
A => liminal_legacy/liminal/assets/shaders/sm/font_shadow.fs
A => liminal_legacy/liminal/assets/shaders/sm/font_shadow.vs
A => liminal_legacy/liminal/assets/shaders/sm/ppfx/blur_evsm.glsl
A => liminal_legacy/liminal/assets/shaders/sm/ppfx/convert_evsm.glsl
A => liminal_legacy/liminal/assets/shaders/sm/shadow.glsl
A => liminal_legacy/liminal/assets/shaders/sm/shadow_frag.glsl
A => liminal_legacy/liminal/assets/shaders/std/attrib.glsl
A => liminal_legacy/liminal/assets/shaders/std/compat.glsl
A => liminal_legacy/liminal/assets/shaders/std/std.glsl
A => liminal_legacy/liminal/assets/shaders/std/ubo.glsl
A => liminal_legacy/liminal/assets/shaders/std/varying.glsl
A => liminal_legacy/liminal/assets/shaders/std/vq.glsl
A => liminal_legacy/liminal/assets/shaders/ui2d/ninepatch2d.glsl
A => liminal_legacy/liminal/assets/shaders/ui2d/quad2d.glsl
A => liminal_legacy/liminal/assets/shaders/ui2d/ui2d.glsl
A => liminal_legacy/liminal/assets/textures/blank.png
A => liminal_legacy/liminal/assets/textures/blender_icons16.png
A => liminal_legacy/liminal/assets/textures/lut_default.png
A => liminal_legacy/liminal/assets/textures/spot.png
A => liminal_legacy/liminal/assets/textures/zero.png
A => liminal_legacy/liminal/blenderclient/__init__.py
A => liminal_legacy/liminal/blenderclient/structure.py
A => liminal_legacy/liminal/bootstrap.py
A => liminal_legacy/liminal/data/__init__.py
A => liminal_legacy/liminal/data/builder.py
A => liminal_legacy/liminal/data/export_blendc.py
A => liminal_legacy/liminal/data/mpc.py
A => liminal_legacy/liminal/data/source.py
A => liminal_legacy/liminal/engine/__init__.py
A => liminal_legacy/liminal/engine/actor.py
A => liminal_legacy/liminal/engine/archive.py
A => liminal_legacy/liminal/engine/audio.py
A => liminal_legacy/liminal/engine/basebuffer.py
A => liminal_legacy/liminal/engine/camera.py
A => liminal_legacy/liminal/engine/config.py
A => liminal_legacy/liminal/engine/core.py
A => liminal_legacy/liminal/engine/deferred.py
A => liminal_legacy/liminal/engine/draw.py
A => liminal_legacy/liminal/engine/fileman.py
A => liminal_legacy/liminal/engine/font.py
A => liminal_legacy/liminal/engine/framebuffer.py
A => liminal_legacy/liminal/engine/group.py
A => liminal_legacy/liminal/engine/hmd.py
A => liminal_legacy/liminal/engine/input.py
A => liminal_legacy/liminal/engine/interface.py
A => liminal_legacy/liminal/engine/legacy_input.py
A => liminal_legacy/liminal/engine/light.py
A => liminal_legacy/liminal/engine/logic.py
A => liminal_legacy/liminal/engine/material.py
A => liminal_legacy/liminal/engine/mesh.py
A => liminal_legacy/liminal/engine/physics.py
A => liminal_legacy/liminal/engine/pixelbuffer.py
A => liminal_legacy/liminal/engine/ppfx.py
A => liminal_legacy/liminal/engine/profiler.py
A => liminal_legacy/liminal/engine/render.py
A => liminal_legacy/liminal/engine/shader.py
A => liminal_legacy/liminal/engine/system.py
A => liminal_legacy/liminal/engine/texture.py
A => liminal_legacy/liminal/engine/timing.py
A => liminal_legacy/liminal/extras/__init__.py
A => liminal_legacy/liminal/extras/adhoc.py
A => liminal_legacy/liminal/extras/animate.py
A => liminal_legacy/liminal/extras/blendfile.py
A => liminal_legacy/liminal/extras/ffi_inspect.py
A => liminal_legacy/liminal/extras/fsm.py
A => liminal_legacy/liminal/extras/fundom.py
A => liminal_legacy/liminal/extras/genimage.py
A => liminal_legacy/liminal/extras/genmesh.py
A => liminal_legacy/liminal/extras/hdr_utils.py
A => liminal_legacy/liminal/extras/movie.py
A => liminal_legacy/liminal/extras/noise.py
A => liminal_legacy/liminal/extras/pdstream.py
A => liminal_legacy/liminal/extras/perlin.py
A => liminal_legacy/liminal/extras/procaudio.py
A => liminal_legacy/liminal/extras/procmesh.py
A => liminal_legacy/liminal/extras/proctexture.py
A => liminal_legacy/liminal/extras/purify.py
A => liminal_legacy/liminal/extras/soilui.py
A => liminal_legacy/liminal/extras/svg.py
A => liminal_legacy/liminal/runtime.py
A => liminal_legacy/liminal/sigil/__init__.py
A => liminal_legacy/liminal/spectre/__init__.py
A => liminal_legacy/liminal/speedups/__init__.py
A => liminal_legacy/liminal/speedups/_speedups.py
A => liminal_legacy/liminal/speedups/builder.py
A => liminal_legacy/liminal/speedups/callbuffer.py
A => liminal_legacy/liminal/speedups/defs/city.cpp
A => liminal_legacy/liminal/speedups/defs/city.h
A => liminal_legacy/liminal/speedups/defs/compat.h
A => liminal_legacy/liminal/speedups/defs/everything.h
A => liminal_legacy/liminal/speedups/defs/imgproc.cpp
A => liminal_legacy/liminal/speedups/defs/imgproc.h
A => liminal_legacy/liminal/speedups/defs/lpv.cpp
A => liminal_legacy/liminal/speedups/defs/lpv.h
A => liminal_legacy/liminal/speedups/defs/nodes.cpp
A => liminal_legacy/liminal/speedups/defs/nodes.h
A => liminal_legacy/liminal/speedups/defs/particles.cpp
A => liminal_legacy/liminal/speedups/defs/particles.h
A => liminal_legacy/liminal/speedups/defs/render.cpp
A => liminal_legacy/liminal/speedups/defs/render.h
A => liminal_legacy/liminal/speedups/defs/sigil.cpp
A => liminal_legacy/liminal/speedups/defs/sigil.h
A => liminal_legacy/liminal/speedups/defs/soil_types.h
A => liminal_legacy/liminal/speedups/defs/soilui.cpp
A => liminal_legacy/liminal/speedups/defs/soilui.h
A => liminal_legacy/liminal/speedups/defs/speedups.c
A => liminal_legacy/liminal/speedups/defs/speedups.h
A => liminal_legacy/liminal/speedups/internal.py
A => liminal_legacy/liminal/speedups/soilui.py
A => liminal_legacy/liminal/utils.py
A => liminal_legacy/setup.py
A => liminal_legacy/testing/__init__.py
A => liminal_legacy/testing/data/axiscube.png
A => liminal_legacy/testing/data/blank_noisy.png
A => liminal_legacy/testing/data/crawler.png
A => liminal_legacy/testing/data/duangle_logo.png
A => liminal_legacy/testing/data/hierarchy.blend
A => liminal_legacy/testing/data/hierarchy.dat
A => liminal_legacy/testing/data/lut_default.png
A => liminal_legacy/testing/data/lut_var1.png
A => liminal_legacy/testing/data/lut_var2.png
A => liminal_legacy/testing/data/lut_var3.png
A => liminal_legacy/testing/data/physics.blend
A => liminal_legacy/testing/data/physics.dat
A => liminal_legacy/testing/data/rotatecube.py
A => liminal_legacy/testing/data/sdftest.png
A => liminal_legacy/testing/data/spawn.blend
A => liminal_legacy/testing/data/spawn.dat
A => liminal_legacy/testing/data/spawn.py
A => liminal_legacy/testing/data/stpeters_cross.hdr
A => liminal_legacy/testing/data/test_1.png
A => liminal_legacy/testing/data/uv_distort.png
A => liminal_legacy/testing/data/uv_distort.svg
A => liminal_legacy/testing/data/uv_distort_abs.png
A => liminal_legacy/testing/data/uv_distort_h.png
A => liminal_legacy/testing/dead/test_changes.py
A => liminal_legacy/testing/dead/test_font.py
A => liminal_legacy/testing/dead/test_hpmc.py
A => liminal_legacy/testing/dead/test_import.py
A => liminal_legacy/testing/dead/test_loadblend.py
A => liminal_legacy/testing/dead/test_node.py
A => liminal_legacy/testing/dead/test_purify.py
A => liminal_legacy/testing/dead/test_utils.py
A => liminal_legacy/testing/eggfont/dosis.beigefont
A => liminal_legacy/testing/eggfont/egg2json.py
A => liminal_legacy/testing/eggfont/json2font.py
A => liminal_legacy/testing/eggfont/mkfont
A => liminal_legacy/testing/eggfont/nevis.beigefont
A => liminal_legacy/testing/eggfont/nevis.egg
A => liminal_legacy/testing/eggfont/nevis_1.png
A => liminal_legacy/testing/gfxcardspecs.txt
A => liminal_legacy/testing/profiling.txt
A => liminal_legacy/testing/startdebug.py
A => liminal_legacy/testing/test.png
A => liminal_legacy/testing/test_cube.py
A => liminal_legacy/testing/test_soilui.py
A => liminal_legacy/testing/types.soil
A => liminal_legacy/utils/cdeftool.py
A => liminal_legacy/utils/install_blenderclient.py
A => liminal_legacy/utils/mkchroot.py
A => portmidi/.hg_archival.txt
A => portmidi/.hgignore
A => portmidi/ALL_BUILD.vcproj
A => portmidi/CHANGELOG.txt
A => portmidi/CMakeLists.txt
A => portmidi/Doxyfile
A => portmidi/README.txt
A => portmidi/ZERO_CHECK.vcproj
A => portmidi/license.txt
A => portmidi/pm_cl/README_CL.txt
A => portmidi/pm_cl/cffi-portmidi.lisp
A => portmidi/pm_cl/test-no-cm.lisp
A => portmidi/pm_common/CMakeLists.txt
A => portmidi/pm_common/pminternal.h
A => portmidi/pm_common/pmjni.vcproj
A => portmidi/pm_common/pmutil.c
A => portmidi/pm_common/pmutil.h
A => portmidi/pm_common/portmidi-dynamic.vcproj
A => portmidi/pm_common/portmidi-static.vcproj
A => portmidi/pm_common/portmidi.c
A => portmidi/pm_common/portmidi.h
A => portmidi/pm_csharp/README.txt
A => portmidi/pm_csharp/pm_managed/AssemblyInfo.cpp
A => portmidi/pm_csharp/pm_managed/ReadMe.txt
A => portmidi/pm_csharp/pm_managed/Stdafx.cpp
A => portmidi/pm_csharp/pm_managed/Stdafx.h
A => portmidi/pm_csharp/pm_managed/app.ico
A => portmidi/pm_csharp/pm_managed/app.rc
A => portmidi/pm_csharp/pm_managed/pm_managed.cpp
A => portmidi/pm_csharp/pm_managed/pm_managed.h
A => portmidi/pm_csharp/pm_managed/pm_managed.vcproj
A => portmidi/pm_csharp/pm_managed/resource.h
A => portmidi/pm_dylib/CMakeLists.txt
A => portmidi/pm_dylib/README.txt
A => portmidi/pm_dylib/portmidi-dynamic.vcproj
A => portmidi/pm_linux/README_LINUX.txt
A => portmidi/pm_linux/finddefault.c
A => portmidi/pm_linux/pmlinux.c
A => portmidi/pm_linux/pmlinux.h
A => portmidi/pm_linux/pmlinuxalsa.c
A => portmidi/pm_linux/pmlinuxalsa.h
A => portmidi/pm_mac/Makefile.osx
A => portmidi/pm_mac/README_MAC.txt
A => portmidi/pm_mac/finddefault.c
A => portmidi/pm_mac/pm_mac.xcodeproj/project.pbxproj
A => portmidi/pm_mac/pmdefaults/make/build.xml
A => portmidi/pm_mac/pmdefaults/make/find-classrefs.sh
A => portmidi/pm_mac/pmdefaults/resources/English.lproj/Credits.rtf
A => portmidi/pm_mac/pmdefaults/resources/English.lproj/InfoPlist.strings
A => portmidi/pm_mac/pmdefaults/resources/Info.plist
A => portmidi/pm_mac/pmdefaults/resources/Manifest
A => portmidi/pm_mac/pmmac.c
A => portmidi/pm_mac/pmmac.h
A => portmidi/pm_mac/pmmacosxcm.c
A => portmidi/pm_mac/pmmacosxcm.h
A => portmidi/pm_mac/readbinaryplist.c
A => portmidi/pm_mac/readbinaryplist.h
A => portmidi/pm_mingw/README-MINGW.txt
A => portmidi/pm_mingw/eclipse/README.txt
A => portmidi/pm_mingw/eclipse/dot-cproject
A => portmidi/pm_mingw/eclipse/dot-project
A => portmidi/pm_mingw/msys/README-MSYS.txt
A => portmidi/pm_python/README_PYTHON.txt
A => portmidi/pm_python/pyportmidi/__init__.py
A => portmidi/pm_python/pyportmidi/_pyportmidi.c
A => portmidi/pm_python/pyportmidi/_pyportmidi.pyx
A => portmidi/pm_python/pyportmidi/midi.py
A => portmidi/pm_python/setup.py
A => portmidi/pm_qt/README_QT.txt
A => portmidi/pm_qt/portmidi.pro
A => portmidi/pm_test/CMakeLists.txt
A => portmidi/pm_test/latency.c
A => portmidi/pm_test/latency.vcproj
A => portmidi/pm_test/midiclock.c
A => portmidi/pm_test/midiclock.vcproj
A => portmidi/pm_test/midithread.c
A => portmidi/pm_test/midithread.vcproj
A => portmidi/pm_test/midithru.c
A => portmidi/pm_test/midithru.vcproj
A => portmidi/pm_test/mm.c
A => portmidi/pm_test/mm.vcproj
A => portmidi/pm_test/qtest.c
A => portmidi/pm_test/qtest.vcproj
A => portmidi/pm_test/sysex.c
A => portmidi/pm_test/sysex.vcproj
A => portmidi/pm_test/test.c
A => portmidi/pm_test/test.vcproj
A => portmidi/pm_test/txdata.syx
A => portmidi/pm_win/README_WIN.txt
A => portmidi/pm_win/clean_cmake.bat
A => portmidi/pm_win/clean_up_vcproj.awk
A => portmidi/pm_win/clean_up_vcproj.bat
A => portmidi/pm_win/debugging_dlls.txt
A => portmidi/pm_win/pmwin.c
A => portmidi/pm_win/pmwinmm.c
A => portmidi/pm_win/pmwinmm.h
A => portmidi/pm_win/static.cmake
A => portmidi/portmidi.sln
A => portmidi/portmidi_cdt.zip
A => portmidi/portmusic_logo.png
A => portmidi/porttime/porttime-VC8.vcproj
A => portmidi/porttime/porttime.c
A => portmidi/porttime/porttime.dsp
A => portmidi/porttime/porttime.h
A => portmidi/porttime/porttime.vcproj
A => portmidi/porttime/ptlinux.c
A => portmidi/porttime/ptmacosx_cf.c
A => portmidi/porttime/ptmacosx_mach.c
A => portmidi/porttime/ptwinmm.c
A => procaudio/.hg_archival.txt
A => procaudio/.hgignore
A => procaudio/README
A => procaudio/procaudio/__init__.py
A => procaudio/procaudio/_procaudioapi.py
A => procaudio/procaudio/builder.py
A => procaudio/procaudio/defs/procaudioapi.cpp
A => procaudio/procaudio/defs/procaudioapi.h
A => procaudio/procaudio/defs/svf.h
A => procaudio/procaudio/extras.py
A => procaudio/procaudio/internal.py
A => procaudio/procaudio/player.py
A => procaudio/setup.py
A => procaudio/testing/test_audio1.py
A => procaudio/utils/mkstk.py
A => procmesh/.hg_archival.txt
A => procmesh/.hgignore
A => procmesh/README
A => procmesh/doc/summary.txt
A => procmesh/procmesh/__init__.py
A => procmesh/procmesh/_procmeshapi.py
A => procmesh/procmesh/builder.py
A => procmesh/procmesh/csg.py
A => procmesh/procmesh/defs/mc.c
A => procmesh/procmesh/defs/perlin.c
A => procmesh/procmesh/defs/perlin.h
A => procmesh/procmesh/defs/perlinhw.c
A => procmesh/procmesh/defs/perlinhw.h
A => procmesh/procmesh/defs/procmeshapi.cpp
A => procmesh/procmesh/defs/procmeshapi.h
A => procmesh/procmesh/defs/qr_solve.c
A => procmesh/procmesh/defs/qr_solve.h
A => procmesh/procmesh/defs/r8lib.c
A => procmesh/procmesh/defs/r8lib.h
A => procmesh/procmesh/defs/saiaaa.h
A => procmesh/procmesh/defs/stb_perlin.h
A => procmesh/procmesh/defs/tetratree.cpp
A => procmesh/procmesh/defs/tetratree.h
A => procmesh/procmesh/dfm.py
A => procmesh/procmesh/implicit/__init__.py
A => procmesh/procmesh/implicit/cl.py
A => procmesh/procmesh/implicit/codegen.py
A => procmesh/procmesh/implicit/dsl.py
A => procmesh/procmesh/implicit/testing.py
A => procmesh/procmesh/internal.py
A => procmesh/procmesh/utils.py
A => procmesh/setup.py
A => procmesh/testing/data/README
A => procmesh/testing/debuglog.txt
A => procmesh/testing/test_sculpt.py
A => procmesh/testing/test_sharxel.py
A => pybullet-cffi/.hg_archival.txt
A => pybullet-cffi/.hgignore
A => pybullet-cffi/README
A => pybullet-cffi/bullet/__init__.py
A => pybullet-cffi/bullet/_bullet.py
A => pybullet-cffi/bullet/builder.py
A => pybullet-cffi/bullet/codegen.py
A => pybullet-cffi/bullet/internal.py
A => pybullet-cffi/bullet/old_builder.py
A => pybullet-cffi/bullet/proxy.py
A => pybullet-cffi/defs/bullet_utils.h
A => pybullet-cffi/defs/bullet_wrapper.cpp
A => pybullet-cffi/defs/bullet_wrapper.h
A => pybullet-cffi/defs/bullet_wrapper_old.cpp
A => pybullet-cffi/defs/bullet_wrapper_old.h
A => pybullet-cffi/mkbullet
A => pybullet-cffi/mkbullet.bat
A => pybullet-cffi/setup.py
A => pybullet-cffi/testing/__init__.py
A => pybullet-cffi/testing/example.py
A => pybullet-cffi/testing/test_bullet.py
A => pybullet-cffi/utils/mkbullet.py
A => pylibpd-cffi/.hg_archival.txt
A => pylibpd-cffi/.hgignore
A => pylibpd-cffi/README
A => pylibpd-cffi/mklibpd
A => pylibpd-cffi/pd/__init__.py
A => pylibpd-cffi/pd/_pd.py
A => pylibpd-cffi/pd/builder.py
A => pylibpd-cffi/pd/internal.py
A => pylibpd-cffi/pd_defs/m_pd.h
A => pylibpd-cffi/predefs/pd.pypredef
A => pylibpd-cffi/setup.py
A => pylibpd-cffi/testing/al_test.py
A => pylibpd-cffi/testing/echo.pd
A => pylibpd-cffi/testing/echo.py
A => pylibpd-cffi/testing/funtest.pd
A => pylibpd-cffi/testing/pygame_fun_test.py
A => pylibpd-cffi/testing/test.pd
A => pylibpd-cffi/testing/test_libpd.py
A => pylibpd-cffi/utils/mklibpd.py
A => pynanosvg/.hg_archival.txt
A => pynanosvg/.hgignore
A => pynanosvg/README
A => pynanosvg/mknanosvg
A => pynanosvg/mknanosvg.bat
A => pynanosvg/nanosvg/__init__.py
A => pynanosvg/nanosvg/_nanosvg.py
A => pynanosvg/nanosvg/builder.py
A => pynanosvg/nanosvg/defs/nanosvg.cpp
A => pynanosvg/nanosvg/defs/prefix.cpp
A => pynanosvg/nanosvg/defs/prefix.h
A => pynanosvg/nanosvg/internal.py
A => pynanosvg/nanosvg/managed.py
A => pynanosvg/predefs/nanosvg.pypredef
A => pynanosvg/setup.py
A => pynanosvg/testing/test_svg.py
A => pynanosvg/utils/mknanosvg.py
A => pynanovg/.hg_archival.txt
A => pynanovg/.hgignore
A => pynanovg/README
A => pynanovg/mknanovg
A => pynanovg/mknanovg.bat
A => pynanovg/nanovg/__init__.py
A => pynanovg/nanovg/_nanovg.py
A => pynanovg/nanovg/builder.py
A => pynanovg/nanovg/defs/prefix.cpp
A => pynanovg/nanovg/defs/prefix.h
A => pynanovg/nanovg/internal.py
A => pynanovg/nanovg/managed.py
A => pynanovg/predefs/nanovg.pypredef
A => pynanovg/setup.py
A => pynanovg/testing/Roboto-Bold.ttf
A => pynanovg/testing/test_nanovg.py
A => pynanovg/utils/mknanovg.py
A => pyode-cffi/.DS_Store
A => pyode-cffi/.hg_archival.txt
A => pyode-cffi/.hgignore
A => pyode-cffi/README
A => pyode-cffi/defs/init.h
A => pyode-cffi/mkode
A => pyode-cffi/mkode.bat
A => pyode-cffi/ode/__init__.py
A => pyode-cffi/ode/_ode.py
A => pyode-cffi/ode/builder.py
A => pyode-cffi/ode/internal.py
A => pyode-cffi/ode/wrapped.py
A => pyode-cffi/setup.py
A => pyode-cffi/testing/__init__.py
A => pyode-cffi/testing/example.py
A => pyode-cffi/utils/mkode.py
A => pyopenal-cffi/.hg_archival.txt
A => pyopenal-cffi/.hgignore
A => pyopenal-cffi/LICENSE
A => pyopenal-cffi/README
A => pyopenal-cffi/al/__init__.py
A => pyopenal-cffi/al/_al.py
A => pyopenal-cffi/al/al.py
A => pyopenal-cffi/al/builder.py
A => pyopenal-cffi/al/internal.py
A => pyopenal-cffi/al/managed.py
A => pyopenal-cffi/al_defs/al.h
A => pyopenal-cffi/al_defs/alc.h
A => pyopenal-cffi/al_defs/alext.h
A => pyopenal-cffi/al_defs/efx-creative.h
A => pyopenal-cffi/al_defs/efx.h
A => pyopenal-cffi/predefs/al.pypredef
A => pyopenal-cffi/setup.py
A => pyopenal-cffi/testing/__init__.py
A => pyopenal-cffi/testing/data/laser.wav
A => pyopenal-cffi/testing/test_al.py
A => pyopencl-cffi/.hg_archival.txt
A => pyopencl-cffi/.hgignore
A => pyopencl-cffi/LICENSE
A => pyopencl-cffi/README
A => pyopencl-cffi/cl/__init__.py
A => pyopencl-cffi/cl/_cl.py
A => pyopencl-cffi/cl/builder.py
A => pyopencl-cffi/cl/defs/cl.h
A => pyopencl-cffi/cl/defs/cl_ext.h
A => pyopencl-cffi/cl/defs/cl_gl.h
A => pyopencl-cffi/cl/defs/cl_gl_ext.h
A => pyopencl-cffi/cl/defs/cl_platform.h
A => pyopencl-cffi/cl/internal.py
A => pyopencl-cffi/cl/managed.py
A => pyopencl-cffi/predefs/cl.pypredef
A => pyopencl-cffi/setup.py
A => pyopencl-cffi/testing/test_cl.py
A => pyopengl-cffi/.hg_archival.txt
A => pyopengl-cffi/.hgignore
A => pyopengl-cffi/LICENSE
A => pyopengl-cffi/README
A => pyopengl-cffi/gl/__init__.py
A => pyopengl-cffi/gl/_corearb.py
A => pyopengl-cffi/gl/builder.py
A => pyopengl-cffi/gl/corearb.py
A => pyopengl-cffi/gl/fallback.py
A => pyopengl-cffi/gl/internal.py
A => pyopengl-cffi/gl/managed.py
A => pyopengl-cffi/gl_defs/glcorearb.h
A => pyopengl-cffi/gl_defs/glext.h
A => pyopengl-cffi/predefs/gl.pypredef
A => pyopengl-cffi/setup.py
A => pyopengl-cffi/testing/__init__.py
A => pyopengl-cffi/testing/gldemo.py
A => pyopengl-cffi/testing/sdl_window.py
A => pyopengl-cffi/testing/window.py
A => pysdl-cffi/.hg_archival.txt
A => pysdl-cffi/.hgignore
A => pysdl-cffi/LICENSE
A => pysdl-cffi/README
A => pysdl-cffi/lua/sdl.cdef
A => pysdl-cffi/lua/test_sdl.lua
A => pysdl-cffi/mksdl
A => pysdl-cffi/mksdl.bat
A => pysdl-cffi/predefs/sdl.pypredef
A => pysdl-cffi/sdl/__init__.py
A => pysdl-cffi/sdl/_sdl.py
A => pysdl-cffi/sdl/builder.py
A => pysdl-cffi/sdl/internal.py
A => pysdl-cffi/sdl_defs/SDL.h
A => pysdl-cffi/sdl_defs/SDL_assert.h
A => pysdl-cffi/sdl_defs/SDL_atomic.h
A => pysdl-cffi/sdl_defs/SDL_audio.h
A => pysdl-cffi/sdl_defs/SDL_blendmode.h
A => pysdl-cffi/sdl_defs/SDL_clipboard.h
A => pysdl-cffi/sdl_defs/SDL_config.h
A => pysdl-cffi/sdl_defs/SDL_cpuinfo.h
A => pysdl-cffi/sdl_defs/SDL_endian.h
A => pysdl-cffi/sdl_defs/SDL_error.h
A => pysdl-cffi/sdl_defs/SDL_events.h
A => pysdl-cffi/sdl_defs/SDL_gesture.h
A => pysdl-cffi/sdl_defs/SDL_haptic.h
A => pysdl-cffi/sdl_defs/SDL_hints.h
A => pysdl-cffi/sdl_defs/SDL_input.h
A => pysdl-cffi/sdl_defs/SDL_joystick.h
A => pysdl-cffi/sdl_defs/SDL_keyboard.h
A => pysdl-cffi/sdl_defs/SDL_keycode.h
A => pysdl-cffi/sdl_defs/SDL_loadso.h
A => pysdl-cffi/sdl_defs/SDL_log.h
A => pysdl-cffi/sdl_defs/SDL_main.h
A => pysdl-cffi/sdl_defs/SDL_mouse.h
A => pysdl-cffi/sdl_defs/SDL_mutex.h
A => pysdl-cffi/sdl_defs/SDL_name.h
A => pysdl-cffi/sdl_defs/SDL_opengl.h
A => pysdl-cffi/sdl_defs/SDL_opengles.h
A => pysdl-cffi/sdl_defs/SDL_opengles2.h
A => pysdl-cffi/sdl_defs/SDL_pixels.h
A => pysdl-cffi/sdl_defs/SDL_platform.h
A => pysdl-cffi/sdl_defs/SDL_power.h
A => pysdl-cffi/sdl_defs/SDL_quit.h
A => pysdl-cffi/sdl_defs/SDL_rect.h
A => pysdl-cffi/sdl_defs/SDL_render.h
A => pysdl-cffi/sdl_defs/SDL_revision.h
A => pysdl-cffi/sdl_defs/SDL_rwops.h
A => pysdl-cffi/sdl_defs/SDL_scancode.h
A => pysdl-cffi/sdl_defs/SDL_shape.h
A => pysdl-cffi/sdl_defs/SDL_stdinc.h
A => pysdl-cffi/sdl_defs/SDL_surface.h
A => pysdl-cffi/sdl_defs/SDL_system.h
A => pysdl-cffi/sdl_defs/SDL_syswm.h
A => pysdl-cffi/sdl_defs/SDL_thread.h
A => pysdl-cffi/sdl_defs/SDL_timer.h
A => pysdl-cffi/sdl_defs/SDL_touch.h
A => pysdl-cffi/sdl_defs/SDL_types.h
A => pysdl-cffi/sdl_defs/SDL_version.h
A => pysdl-cffi/sdl_defs/SDL_video.h
A => pysdl-cffi/sdl_defs/begin_code.h
A => pysdl-cffi/sdl_defs/close_code.h
A => pysdl-cffi/sdl_defs/prefix.h
A => pysdl-cffi/setup.py
A => pysdl-cffi/testing/__init__.py
A => pysdl-cffi/testing/test_clipboard.py
A => pysdl-cffi/testing/test_window.py
A => pysdl-cffi/utils/mksdl.py
A => pysdl-cffi/utils/sdl-osx-gl3.2.patch
A => pystbimage/.hg_archival.txt
A => pystbimage/.hgignore
A => pystbimage/LICENSE
A => pystbimage/README
A => pystbimage/predefs/stbimage.pypredef
A => pystbimage/setup.py
A => pystbimage/stbimage/__init__.py
A => pystbimage/stbimage/_stbimage.py
A => pystbimage/stbimage/builder.py
A => pystbimage/stbimage/defs/prefix.h
A => pystbimage/stbimage/defs/stb_image.c
A => pystbimage/stbimage/defs/stb_image_write.c
A => pystbimage/stbimage/internal.py
A => pystbimage/testing/test.png
A => pystbimage/testing/test_load.py
A => pystbvorbis/.hg_archival.txt
A => pystbvorbis/.hgignore
A => pystbvorbis/README
A => pystbvorbis/predefs/stbvorbis.pypredef
A => pystbvorbis/setup.py
A => pystbvorbis/stbvorbis/__init__.py
A => pystbvorbis/stbvorbis/_stbvorbis.py
A => pystbvorbis/stbvorbis/builder.py
A => pystbvorbis/stbvorbis/defs/prefix.h
A => pystbvorbis/stbvorbis/defs/stb_vorbis.c
A => pystbvorbis/stbvorbis/internal.py
A => pystbvorbis/testing/__init__.py
A => pystbvorbis/testing/data/39459__the-bizniss__laser.ogg
A => pystbvorbis/testing/data/46504__phreaksaccount__shipengine.ogg
A => pystbvorbis/testing/test_load.py
A => pytess2/.hg_archival.txt
A => pytess2/.hgignore
A => pytess2/README
A => pytess2/mktess
A => pytess2/mktess.bat
A => pytess2/predefs/tess.pypredef
A => pytess2/setup.py
A => pytess2/tess/__init__.py
A => pytess2/tess/_tess.py
A => pytess2/tess/builder.py
A => pytess2/tess/defs/prefix.cpp
A => pytess2/tess/defs/prefix.h
A => pytess2/tess/internal.py
A => pytess2/tess/managed.py
A => pytess2/utils/mktess.py
A => python-awesomium-cffi/.hg_archival.txt
A => python-awesomium-cffi/.hgignore
A => python-awesomium-cffi/LICENSE
A => python-awesomium-cffi/README
A => python-awesomium-cffi/awesomium/__init__.py
A => python-awesomium-cffi/awesomium/_awesomium.py
A => python-awesomium-cffi/awesomium/builder.py
A => python-awesomium-cffi/awesomium/internal.py
A => python-awesomium-cffi/awesomium/keycodes.py
A => python-awesomium-cffi/awesomium/proxy.py
A => python-awesomium-cffi/awesomium/sdlutils.py
A => python-awesomium-cffi/defs/prefix.h
A => python-awesomium-cffi/predefs/awesomium.pypredef
A => python-awesomium-cffi/setup.py
A => python-awesomium-cffi/testing/__init__.py
A => python-awesomium-cffi/testing/test_awe.py
A => python-awesomium-cffi/utils/mkawesomium.py
A => python-glm/.hg_archival.txt
A => python-glm/.hgignore
A => python-glm/LICENSE
A => python-glm/README
A => python-glm/glm/__init__.py
A => python-glm/glm/_functions.py
A => python-glm/glm/_glmapi.py
A => python-glm/glm/builder.py
A => python-glm/glm/defs/glmapi.cpp
A => python-glm/glm/defs/glmapi.h
A => python-glm/glm/functions.py
A => python-glm/glm/internal.py
A => python-glm/glm/types.py
A => python-glm/setup.py
A => python-glm/testing/__init__.py
A => python-glm/testing/performance.py
A => python-glm/testing/test_glm.py
A => python-ovr/.hg_archival.txt
A => python-ovr/.hgignore
A => python-ovr/README
A => python-ovr/defs/ovr_utils.h
A => python-ovr/defs/ovr_wrapper.cpp
A => python-ovr/defs/ovr_wrapper.h
A => python-ovr/mkovr
A => python-ovr/mkovr.bat
A => python-ovr/ovr/__init__.py
A => python-ovr/ovr/_ovr.py
A => python-ovr/ovr/builder.py
A => python-ovr/ovr/codegen.py
A => python-ovr/ovr/internal.py
A => python-ovr/ovr/proxy.py
A => python-ovr/setup.py
A => python-ovr/testing/__init__.py
A => python-ovr/testing/example.py
A => python-ovr/utils/Makefile
A => python-ovr/utils/mkovr.py
A => python-tcc/.hg_archival.txt
A => python-tcc/.hgignore
A => python-tcc/README
A => python-tcc/mktcc
A => python-tcc/mktcc.bat
A => python-tcc/predefs/tcc.pypredef
A => python-tcc/setup.py
A => python-tcc/tcc/__init__.py
A => python-tcc/tcc/_tcc.py
A => python-tcc/tcc/builder.py
A => python-tcc/tcc/internal.py
A => python-tcc/tcc/managed.py
A => python-tcc/testing/simple.ispc
A => python-tcc/testing/test_ispc.py
A => python-tcc/testing/test_tcc.py
A => python-tcc/utils/mktcc.py
A => soil/.cproject
A => soil/.hg_archival.txt
A => soil/.hgignore
A => soil/CREDITS.md
A => soil/LICENSE.md
A => soil/README.md
A => soil/include/soil/meta.h
A => soil/include/soil/soil.h
A => soil/include/soil/soil_spec.h
A => soil/include/soil/table.h
A => soil/include/soil/toc.h
A => soil/include/soil_builder.h
A => soil/mksoil.bat
A => soil/premake4.lua
A => soil/setup.py
A => soil/soil/__init__.py
A => soil/soil/_soil.py
A => soil/soil/builder.py
A => soil/soil/internal.py
A => soil/soil/managed.py
A => soil/soil/spec/__init__.py
A => soil/soil/spec/generator.py
A => soil/soil/spec/lexer.py
A => soil/soil/spec/parser.py
A => soil/soil/spec/preprocess.py
A => soil/src/meta.cpp
A => soil/src/soil.cpp
A => soil/src/table.cpp
A => soil/src/toc.cpp
A => soil/testing/embed_test.c
A => soil/testing/test.c
A => soil/testing/test_allocs.py
A => soil/testing/test_volume.py
A => soil/testing/test_volume_spec.h
A => soil/utils/mksoil.py
A => tentacle/.hg_archival.txt
A => tentacle/.hgignore
A => tentacle/README
A => tentacle/__tentacle__
A => tentacle/scripts/distribute_setup.py
A => tentacle/scripts/get-pip.py
A => tentacle/setup.py
A => tentacle/tentacle/__init__.py
A => tentacle/tt
A => tentacle/tt.bat
A => autobind-cffi/.hg_archival.txt +6 -0
@@ 0,0 1,6 @@ 
+repo: 1f712da4b8a6d6a40aa8db53c48c02f3dd1fdb39
+node: 3e563174f4df61f33660c2327861bf03ed5b0913
+branch: default
+latesttag: null
+latesttagdistance: 14
+changessincelatesttag: 14

          
A => autobind-cffi/.hgignore +24 -0
@@ 0,0 1,24 @@ 
+syntax: glob
+
+.sconsign.dblite
+.settings
+*.egg-info
+*.pyc
+*.blend1
+*.blend2
+*.avi
+*.glc
+*.mp4
+*.p3d
+debug.txt
+__pycache__
+*.orig
+*.bgeconf
+.project
+.pydevproject
+
+syntax: regexp
+
+^build
+^dist
+

          
A => autobind-cffi/LICENSE +26 -0
@@ 0,0 1,26 @@ 
+
+Except when otherwise stated (look for LICENSE files in directories or
+information at the beginning of each file) all software and
+documentation is licensed as follows: 
+
+    The MIT License
+
+    Permission is hereby granted, free of charge, to any person 
+    obtaining a copy of this software and associated documentation 
+    files (the "Software"), to deal in the Software without 
+    restriction, including without limitation the rights to use, 
+    copy, modify, merge, publish, distribute, sublicense, and/or 
+    sell copies of the Software, and to permit persons to whom the 
+    Software is furnished to do so, subject to the following conditions:
+
+    The above copyright notice and this permission notice shall be included 
+    in all copies or substantial portions of the Software.
+
+    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 
+    OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
+    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 
+    THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
+    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 
+    FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 
+    DEALINGS IN THE SOFTWARE.
+

          
A => autobind-cffi/README +1 -0
@@ 0,0 1,1 @@ 
+Set of utility classes to help with writing Python-CFFI bindings for C classes. Acts as development/build-time dependency only.

          
A => autobind-cffi/autobind/__init__.py +489 -0
@@ 0,0 1,489 @@ 
+
+from __future__ import (print_function, division, absolute_import)
+
+# code generator
+
+import os
+import sys
+from tempfile import mktemp
+from subprocess import PIPE, Popen
+
+import re
+import traceback
+from cffi import FFI, CDefError, verifier
+from StringIO import StringIO
+from fnmatch import fnmatch
+
+IS_WIN32 = sys.platform == 'win32'
+
+# on windows, mingw\bin must be in path
+CPP_PATH = "cpp"
+
+################################################################################
+
+_TEMPLATE = """
+# this file is auto-generated. do not edit.
+from __future__ import (print_function, division, absolute_import)
+
+from cffi import FFI
+
+UNMANGLED_EXPORTS = [
+{all_unmangled}
+]
+
+EXPORTS = [
+{all}
+]
+
+INTERNAL_EXPORTS = [
+'_ffi',
+'EXPORTS',
+'UNMANGLED_EXPORTS',
+]
+
+__all__ = EXPORTS + UNMANGLED_EXPORTS + INTERNAL_EXPORTS
+
+from .internal import *
+
+_LIB = load_lib(\"""
+{cdefs}
+\""")
+
+{imports}
+{post_template}
+"""
+
+_IMPORT_FUNC_TEMPLATE = "{module_name} = lookup('{name}')\n"
+
+_IMPORT_SAFE_FUNC_TEMPLATE = "{module_name} = guard(lookup('{name}'))\n"
+
+################################################################################
+
+class AutoBind(object):
+    def __init__(self, options):
+        """
+        options to pass (as dict):
+        
+        CDEF_PATH: path string to the folder where all cdef files are located
+        CDEF_FILES: a list of include files to parse, in correct order
+        FFI_INCLUDE: a list of ffi objects to include using FFI.include()
+        DEFINES_BLACKLIST: a list of defined symbols that should not be exported
+        DEFINES: a list of defines that help to resolve #ifdef blocks
+        PRIVATE_SYMBOLS: a list of functions that should be prepended with an underscore
+        AUTOCHECK_BLACKLIST: a list of functions that should not be wrapped with guard()
+        REPLACES: a list of tuples (A, B) to be applied for string replacement
+        PRE_REPLACES: a list of tuples (A, B) to be pre-applied for string replacement
+        LIBNAME: name of library that contains the symbols (e.g. "SDL2", "GL", ...)
+        VERIFY_SOURCE: source code containing includes necessary to verify cdefs
+        VERIFY_OPTIONS: dictionary of arguments to pass to verify (optional override)
+        AUTOMANGLE: if True, prefix an underscore to all functions dealing with pointers
+        PYPREDEFS: path to pypredefs to generate for editors such as Aptana Studio
+        OUTMODULE: path to python module that should be generated
+        GENPOSTFIX: string to append to generated module (optional)
+        STRIP: remove comments (default: true)
+        PREPROCESS: preprocess source (default: false)
+        PREPROCESS_OPTIONS: options for preprocessor, if PREPROCESS is True
+        DEBUGOUT: write debug.txt containing intermediate defs
+        FINDERROR: remove statements until problem is gone
+        AUXCACHEDIR: auxiliarly cache dir to be cleaned on build
+        """
+        self.options = options
+    
+    def build_cdefs(self):
+        options = self.options
+        
+        re_ifdef = re.compile(r'^\#\S+\s+(\S+)')
+        re_define = re.compile(r'^\#define\s+(\S+)(\s+\S+)?')
+        re_hex = re.compile(r'(.*)(0x[0-9A-Fa-f]+)([^0-9A-Fa-f]*.*)')
+        
+        CDEF_PATH = options['CDEF_PATH']
+        PREPROCESS = options.get('PREPROCESS', False)
+        CDEF_FILES = options.get('CDEF_FILES', [])
+        _DEFINES_BLACKLIST = set(options.get('DEFINES_BLACKLIST', []))
+        _DEFINES = set(options.get('DEFINES', []))
+        _PRIVATE_SYMBOLS = set(options.get('PRIVATE_SYMBOLS', []))
+        _AUTOCHECK_BLACKLIST = set(options.get('AUTOCHECK_BLACKLIST', []))
+        REPLACES = options.get('REPLACES', [])
+        REPLACES.sort(key=(lambda k: k[0]), reverse=True)
+        PRE_REPLACES = options.get('PRE_REPLACES', [])
+        PRE_REPLACES.sort(key=(lambda k: k[0]), reverse=True)
+    
+        assert os.path.isdir(CDEF_PATH) 
+        
+        cdefs = StringIO()
+        hdefs = StringIO()
+        
+        for cdef_path in CDEF_FILES:
+            local_defines = set(_DEFINES)
+            
+            cdef_path = os.path.join(CDEF_PATH, cdef_path)
+            with open(cdef_path,'r') as cdef_file:
+                cdef_header = cdef_file.read()
+            
+            for a,b in PRE_REPLACES:
+                cdef_header = cdef_header.replace(a, b)
+                
+            if PREPROCESS:
+                cdef_header = self.preprocess_cdefs(cdef_header)
+    
+            clean_cdefs = ''
+            hdefs.write(cdef_header + '\n')
+            
+            blockdesc = []
+            blockdef = []
+            
+            def active_block_desc():
+                return blockdesc[-1]
+            def is_blocked():
+                return blockdef and (blockdef[-1] == False)
+            def is_meta_blocked():
+                return (len(blockdef) > 1) and (blockdef[-2] == False)
+            def block_push(value,line):
+                blockdesc.append(line)
+                blockdef.append(value)
+            def block_pop():
+                blockdesc.pop()
+                blockdef.pop()
+            def block_flip():
+                blockdef[-1] = not blockdef[-1] 
+            
+            eat_next = False
+            for line in cdef_header.splitlines():
+                line = line.strip()
+                # fix OCD preprocessor statements 
+                if line.startswith('#'):
+                    line = '#' + line[1:].strip()
+                if 0:
+                    while '0x' in line:
+                        # convert hexadecimal numbers to decimal ones
+                        m = re_hex.search(line)
+                        line = m.group(1) + str(int(m.group(2),16)) + m.group(3)
+                if eat_next:
+                    if not line.endswith('\\'):
+                        eat_next = False
+                elif line.startswith('#endif'):
+                    block_pop()
+                elif line.startswith('#ifndef '):
+                    if is_blocked():
+                        block_push(False, line)
+                    else:
+                        m = re_ifdef.match(line)
+                        block_push(not (m.group(1) in local_defines), line)
+                elif line.startswith('#ifdef '):
+                    if is_blocked():
+                        block_push(False, line)
+                    else:
+                        m = re_ifdef.match(line)
+                        block_push(m.group(1) in local_defines, line)
+                elif line.startswith('#if '):
+                    print('excluding',line)               
+                    block_push(False, line)
+                elif line.startswith('#elif'):
+                    print('excluding',line)
+                elif line.startswith('#else'):
+                    if is_meta_blocked():
+                        pass
+                    else:
+                        block_flip()
+                        #block_push(not is_blocked(), line)
+                elif is_blocked():
+                    clean_cdefs += '//' + line + ' | excluded by: %s\n' % (active_block_desc(),)
+                elif line.startswith('#include'):
+                    continue
+                elif line.startswith('#pragma'):
+                    continue
+                elif line.startswith('#error'):
+                    continue
+                elif line.startswith('#undef'):
+                    continue
+                elif line.startswith('#define'):
+                    m = re_define.match(line)
+                    define = m.group(1)
+                    var = m.group(2)
+                    if line.endswith('\\'):
+                        eat_next = True
+                    if '(' in define:
+                        # skip function macros
+                        continue
+                    local_defines.add(define)
+                    if not var:
+                        print('+',define)
+                    elif not (define in _DEFINES_BLACKLIST) and not define.startswith('_'):
+                        var = var.strip()                   
+                        clean_cdefs += '#define ' + define + ' ... // ' + var + '\n'
+                elif line:
+                    clean_cdefs += line + '\n'
+            
+            # remove superfluous macros
+            for a,b in REPLACES:
+                clean_cdefs = clean_cdefs.replace(a, b)
+            
+            cdefs.write(clean_cdefs + '\n')
+            
+        return cdefs.getvalue(), hdefs.getvalue()
+
+    def preprocess_cdefs(self, cdefs):
+        options = self.options
+        CDEF_PATH = options['CDEF_PATH']
+        PREPROCESS_OPTIONS = options.get('PREPROCESS_OPTIONS', [])
+        
+        tmppath = mktemp(suffix='.h')
+        file(tmppath, 'w').write(cdefs)
+        cmdline = ' '.join([
+            CPP_PATH,
+            '-I',CDEF_PATH,
+        ] + PREPROCESS_OPTIONS + [
+            '-P','-dD',
+            '-E',tmppath
+        ])
+        print(cmdline)
+        pipe = Popen(cmdline, shell=True, stdout=PIPE, universal_newlines=True)
+        text = pipe.communicate()[0]
+        outtext = ''
+        for line in text.splitlines():
+            line = line.strip()
+            if not line:
+                continue
+            outtext += line + '\n'
+        print(outtext)
+        return outtext
+
+    def remove_comments(self, cdefs):
+        """List cdefs in the current folder sorted by include order"""
+        tmppath = mktemp(suffix='.h')
+        file(tmppath, 'w').write(cdefs)
+        cmdline = CPP_PATH + ' -P -fpreprocessed -dD -E ' + tmppath
+        pipe = Popen(cmdline, shell=True, stdout=PIPE, universal_newlines=True)
+        text = pipe.communicate()[0]
+        outtext = ''
+        for line in text.splitlines():
+            line = line.strip()
+            if not line:
+                continue
+            outtext += line + '\n'
+        return outtext
+
+    def test_cdefs(self,cdefs,hdefs):
+        options = self.options
+        LIBNAME = options.get('LIBNAME',None)
+        DEF_VERIFY_OPTIONS = dict(
+        )
+        
+        if LIBNAME:
+            DEF_VERIFY_OPTIONS['libraries'] = [LIBNAME]
+
+        VERIFY_SOURCE = options.get('VERIFY_SOURCE', hdefs)
+        VERIFY_OPTIONS = options.get('VERIFY_OPTIONS', DEF_VERIFY_OPTIONS)
+        FFI_INCLUDE = options.get('FFI_INCLUDE', [])
+        AUXCACHEDIR = options.get('AUXCACHEDIR',None)
+        
+        verifier.cleanup_tmpdir()
+        if AUXCACHEDIR is not None and AUXCACHEDIR.endswith('__pycache__'):
+            verifier.cleanup_tmpdir(tmpdir=AUXCACHEDIR)
+        
+        ffi = FFI()
+        for sub_ffi in FFI_INCLUDE:
+            ffi.include(sub_ffi) 
+        ffi.cdef(cdefs)
+        print(VERIFY_OPTIONS)
+        capi = ffi.verify(VERIFY_SOURCE, 
+            **VERIFY_OPTIONS)
+        # crosscheck
+        outcapi = {}
+        if LIBNAME:
+            dlffi = FFI()
+            for sub_ffi in FFI_INCLUDE:
+                dlffi.include(sub_ffi) 
+            dlffi.cdef(cdefs)
+            dlcapi = dlffi.dlopen(LIBNAME)
+        else:
+            dlffi = ffi
+            dlcapi = capi
+        for name,value in capi.__dict__.items():
+            if name.startswith('__'):
+                print("skipping",name)
+                continue
+            if callable(value):
+                dlvalue = getattr(dlcapi, name)
+                outcapi[name] = dlvalue
+            elif isinstance(value, (int,float,long)):
+                outcapi[name] = value
+            else:
+                print("ignoring",name,value)
+        return dlffi, outcapi
+
+    def write_pypredef(self,CAPI):
+        PYPREDEFS = self.options['PYPREDEFS']
+        
+        dirpath = os.path.dirname(PYPREDEFS)
+        if not os.path.isdir(dirpath):
+            os.makedirs(dirpath)
+        
+        filepath = PYPREDEFS
+        
+        print("writing",filepath)
+        with file(filepath, 'w') as f:
+            for name,value in CAPI.items():
+                if name.startswith('__'):
+                    continue
+                if callable(value):
+                    f.write('def {name}(*argv):\n    """{name}"""\n\n'.format(
+                        name=name))
+                if isinstance(value, (int,float,long)):
+                    f.write('{name} = {typeid}\n'.format(name=name,typeid=type(value).__name__))
+
+    def safeguard_blacklisted(self,name):
+        _AUTOCHECK_BLACKLIST = set(self.options.get('AUTOCHECK_BLACKLIST',[]))
+        if name in _AUTOCHECK_BLACKLIST:
+            return True
+        for entry in _AUTOCHECK_BLACKLIST:
+            if fnmatch(name, entry):
+                return True
+        return False
+
+    def write_module(self,cdefs,hdefs,ffi,CAPI):
+        options = self.options
+        _PRIVATE_SYMBOLS = set(options.get('PRIVATE_SYMBOLS',[]))
+        AUTOMANGLE = options.get('AUTOMANGLE',False)
+        OUTMODULE = options['OUTMODULE']
+        
+        f = StringIO()
+        
+        imports = ''
+        exports = {
+            'all' : '',
+            'all_unmangled' : '',
+        }
+        
+        def get_signature(value):
+            #resultsig = ffi.typeof(value).result
+            sig = ffi.typeof(value).args
+            return sig
+        
+        def is_strptr_type(ctype):
+            return "'unsigned char *'" in repr(arg)
+        
+        def is_ptr_type(ctype):
+            cname = ctype.cname 
+            if ctype.cname == 'char *':
+                return False
+            return " *" in ctype.cname
+    
+        for name in sorted(CAPI):
+            value = CAPI[name]
+            module_name = name        
+            export_target = 'all'
+    
+            if callable(value):
+                func_template = "lookup('{name}')"
+                
+                mangle = False            
+                if name in _PRIVATE_SYMBOLS:
+                    mangle = True
+                elif AUTOMANGLE:
+                    sig = get_signature(value)
+                    for i,arg in enumerate(sig):
+                        if is_ptr_type(arg):
+                            print(name,"needs mangling")
+                            mangle = True
+                            break
+                    
+                if mangle:
+                    module_name = '_' + module_name
+                    export_target = 'all_unmangled'
+                    
+                if not self.safeguard_blacklisted(name):
+                    func_template = "guard({func_template})".format(**locals())
+                    
+                func_template = func_template.format(**locals())
+                imports += "{module_name} = {func_template}\n".format(**locals())
+                    
+            elif isinstance(value, (int,float,long)):
+                imports += '{module_name} = {value}\n'.format(**locals())
+    
+            exports[export_target] +=  '"{module_name}",\n'.format(**locals())
+    
+        all = exports['all']
+        all_unmangled = exports['all_unmangled']
+    
+        post_template = options.get('GENPOSTFIX','')
+    
+        f.write(_TEMPLATE.format(**locals()))
+        
+        filepath = OUTMODULE
+        print("writing",filepath)
+        with open(filepath, 'w') as outfile:
+            outfile.write(f.getvalue())
+
+    def find_error(self,cdefs):
+        lines = cdefs.splitlines()
+
+        FFI_INCLUDE = self.options.get('FFI_INCLUDE', [])
+        
+        print("backtracking for broken part...")
+        upper = len(lines)
+        for step in (256,128,64,32,16,1):
+            last_parser_error = 0
+            offset = upper
+            while offset > 0:
+                print(upper, offset)
+                source = '\n'.join(lines[:offset])
+                try:
+                    test_ffi = FFI()
+                    for sub_ffi in FFI_INCLUDE:
+                        test_ffi.include(sub_ffi) 
+                    test_ffi.cdef(source)
+                    break
+                except CDefError:
+                    last_parser_error = offset
+                    offset += 1
+                except:
+                    print(lines[offset-1])
+                    #traceback.print_exc()
+                    upper = offset
+                    offset -= step
+                    if offset == last_parser_error:
+                        break
+            
+        with file('debug.txt','w') as f:
+            print("writing to debug.txt...")
+            part_a = '\n'.join(lines[:offset])
+            part_b = '\n'.join(lines[offset:])
+            f.write(part_a)
+            f.write('\n <<<<<< BROKEN PART AROUND HERE >>>>>> \n')
+            f.write(part_b)
+            return
+
+    def build(self):
+        cdefs,hdefs = self.build_cdefs()
+        if self.options.get('STRIP',True):
+            cdefs = self.remove_comments(cdefs)
+        if self.options.get('DEBUGOUT',False):
+            print("writing debug.txt...")
+            with open('debug.txt','w') as f:
+                f.write(cdefs)
+        try:    
+            ffi, CAPI = self.test_cdefs(cdefs,hdefs)
+        except:
+            traceback.print_exc()
+            if self.options.get('FINDERROR', False):
+                self.find_error(cdefs)
+            raise SystemExit, 255
+        self.write_module(cdefs, hdefs, ffi, CAPI)
+        if self.options.get('PYPREDEFS',None):
+            self.write_pypredef(CAPI)
+
+################################################################################
+
+def preprocess_c_header(text, cpp_options=[], cpp_path='mcpp'):
+    cmdline = ' '.join([
+        cpp_path,
+    ] + cpp_options + [
+        '-DSOIL_CDEF',
+        '-P',
+    ])
+    pipe = Popen(cmdline, shell=True, 
+            stdin=PIPE, stdout=PIPE, universal_newlines=True)
+    result = pipe.communicate(input=text)[0]    
+    assert pipe.returncode == 0
+    return result

          
A => autobind-cffi/setup.py +24 -0
@@ 0,0 1,24 @@ 
+
+import os
+from setuptools import setup
+
+def read(fname):
+    return open(os.path.join(os.path.dirname(__file__), fname)).read()
+
+setup(
+    name = "autobind-cffi",
+    version = "0.1",
+    author = "Leonard Ritter",
+    author_email = "contact@leonard-ritter.com",
+    description = ("Set of utility classes to help with writing Python-CFFI "
+        "bindings for C classes. Acts as development/build-time dependency only."),
+    license = "MIT",
+    keywords = "cffi pypy bindings",
+    url = "https://bitbucket.org/duangle/autobind-cffi",
+    packages=['autobind'],
+    long_description=read('README'),
+    classifiers=[
+        "Development Status :: 3 - Alpha",
+        "License :: OSI Approved :: MIT License",
+    ],
+)
  No newline at end of file

          
A => glue.py/.hg_archival.txt +6 -0
@@ 0,0 1,6 @@ 
+repo: 60193f992356f6a6471e5ce769378b97ee892a1d
+node: 6e73417591f358b67f8f90f2ac81199bc1343237
+branch: default
+latesttag: release-0.5.1
+latesttagdistance: 31
+changessincelatesttag: 31

          
A => glue.py/.hgignore +14 -0
@@ 0,0 1,14 @@ 
+syntax: glob
+
+*.pyc
+__pycache__
+*.orig
+*.egg-info
+.project
+.pydevproject
+
+syntax: regexp
+
+^build
+^dist
+

          
A => glue.py/.hgtags +2 -0
@@ 0,0 1,2 @@ 
+b5e9614dbabbcad87f5d1ab25e506f102878b836 release-0.5
+0486868d6710e250ebd8a9f8ff7dc63a81cb77f0 release-0.5.1

          
A => glue.py/LICENSE +26 -0
@@ 0,0 1,26 @@ 
+
+Except when otherwise stated (look for LICENSE files in directories or
+information at the beginning of each file) all software and
+documentation is licensed as follows: 
+
+    The MIT License
+
+    Permission is hereby granted, free of charge, to any person 
+    obtaining a copy of this software and associated documentation 
+    files (the "Software"), to deal in the Software without 
+    restriction, including without limitation the rights to use, 
+    copy, modify, merge, publish, distribute, sublicense, and/or 
+    sell copies of the Software, and to permit persons to whom the 
+    Software is furnished to do so, subject to the following conditions:
+
+    The above copyright notice and this permission notice shall be included 
+    in all copies or substantial portions of the Software.
+
+    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 
+    OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
+    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 
+    THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
+    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 
+    FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 
+    DEALINGS IN THE SOFTWARE.
+

          
A => glue.py/README.text +209 -0
@@ 0,0 1,209 @@ 
+glue.py is a single lightweight utility module expanding Python with new 
+powerful idioms for accessors, callsets, weak methods, enums, singletons and 
+observable primitives that can easily be integrated with existing projects. It
+is particularly useful for writing model/view oriented applications.
+
+Installation
+============
+
+You'll require Python 2.7, Python 3+ or PyPy 1.9 to use glue.py. Earlier
+versions are not supported.
+
+glue.py can easily be installed from the Python package repository using
+distutils or pip:
+
+    pip install glue.py
+
+If you plan to make amendments to the codebase, or you're interested in the 
+latest development release, it is best to check out the source tree and
+install glue.py in development mode:
+
+    python setup.py develop
+
+Usage
+=====
+
+glue.py provides a larger set of interoperating utility classes and functions,
+of which only the most important ones will be described here in detail. It's
+not a bad idea to just browse through the source and look what else is there
+besides what's covered in this introduction.
+
+Generated Accessors
+-------------------
+
+While Python supports accessors through properties, providing regulated access
+to class variables has been traditionally cumbersome and repetitive, even though
+the syntax has somewhat improved with recent versions. Consider this classical
+approach to property access:
+
+    from types import NoneType
+
+    class Sponge(object):
+        pass
+
+    class Pineapple(object):
+        on_owner_changed = None
+        
+        def __init__(self):
+            self.__owner = None
+            
+        def get_owner(self):
+            return self.__owner
+        def set_owner(self, value):
+            if value == self.__owner:
+                return
+            assert isinstance(value, (Sponge, NoneType))
+            self.__owner = value
+            if self.on_owner_changed:
+                self.on_owner_changed(self)
+        owner = property(get_owner, set_owner, 
+            doc = "the owner of this Pineapple")
+
+apart from preventing a rewrite, the setter also provides an optional hook that
+can be triggered when the value changes. Notice how many times the word "owner"
+has to be repeated in one form or the other in order to expose this relatively
+simple attribute. Until Python 2.7, most people adapted this handy recipe to
+cut down on repetition:
+
+         def owner():
+            doc = """the owner of this pineapple"""
+            def fget(self):
+                return self.__owner
+            def fset(self, value):
+                if value == self.__owner:
+                    return
+                assert isinstance(value, (Sponge, NoneType))
+                self.__owner = value
+                if self.on_owner_changed:
+                    self.on_owner_changed(self)
+            return property(**locals())
+        owner = owner()
+
+That's slightly better, but not perfect, and also a little convoluted. These
+days, this approach is the status quo:
+
+        @property
+        def owner(self):
+            """The owner of this pineapple"""
+            return self.__owner
+        @owner.setter
+        def owner(self, value):
+            if value == self.__owner:
+                return
+            assert isinstance(value, (Sponge, NoneType))
+            self.__owner = value
+            if self.on_owner_changed:
+                self.on_owner_changed(self)
+
+it seems like we're mostly shuffling details around... all three solutions
+vary around 12 lines, and with a bunch of these, classes may seem a lot larger
+than they are. This is how glue.py does it:
+
+    from glue import (lazydecls, defproperty, autoinit, callset)
+    from types import NoneType
+
+    class Sponge(object):
+        pass
+
+    @lazydecls
+    class Pineapple(object):
+        on_owner_changed = callset()
+        
+        owner = defproperty(
+            default = None, types=(Sponge, NoneType), hook = on_owner_changed, 
+            doc = "The owner of this pineapple")
+        
+        def __init__(self):
+            autoinit()
+
+...case closed. For more information, try `help(glue.defproperty)`. 
+
+Callable Sets
+-------------
+
+You may have noticed that the `Pineapple` class exposes the `on_owner_changed`
+callback as a `callset`, which is just a short way of instantiating a
+`CallableSet`. Callable sets are a fast way to create observables:
+
+    from glue import callset
+        
+    def func1(value): 
+        print("func1 called with value",value)
+        return value+10
+
+    def func2(value): 
+        print("func2 called with value",value)
+        return value+20
+
+    def func3(value): 
+        print("func3 called with value",value)
+        return value+30
+
+Trying this on the command line:
+
+    >>> funcs = callset([func1,func2,func3])
+    >>> print(max(funcs(10)))
+    func1 called with value 10
+    func2 called with value 10
+    func3 called with value 10
+    40
+
+In the pineapple example, this is how a view would typically subscribe to
+events sent by the `Pineapple` class:
+
+    @lazydecls
+    class Squid(object):
+        neighbor_house = defproperty()
+        annoying_coworkers = defproperty(default = set)
+        
+        def __init__(self, **kwargs):
+            # auto-assign matching keyword args
+            autoinit(**kwargs)
+            # track if the owner of the neighbor house changes by registering
+            # Squid's callback. The callback will be weakly proxied, and
+            # automatically unregisters when this Squid instance is no longer
+            # referenced. 
+            self.neighbor_house.on_owner_changed.addweak(self.someone_moved_in)
+            
+        def someone_moved_in(self, event):
+            # since the on_owner_changed callset is global to Pineapple, Squid
+            # would be notified of any owner changes in Pineapples, so filter
+            # for the right one.
+            if not (event.instance is self.neighbor_house):
+                return
+            # check if the new tenant is one of our annoying coworkers
+            if event.value in self.annoying_coworkers:
+                # state opinion on the situation
+                print("Meh.")
+    
+
+Trying this on the command line:
+
+    >>> house = Pineapple()
+    >>> bob = Sponge()
+    >>> squidward = Squid(neighbor_house = house)
+    >>> squidward.annoying_coworkers.add(bob)
+    >>> house.owner = bob
+    Meh.
+
+And that's the gist of it.
+
+Weak Callable Proxies
+---------------------
+
+TODO
+
+Observable Dicts, Lists and Sets
+--------------------------------
+
+TODO
+
+Enums
+-----
+
+TODO
+
+Singletons
+----------
+
+TODO

          
A => glue.py/glue.py +1539 -0
@@ 0,0 1,1539 @@ 
+# glue.py - agnostic model/view toolshed for Python 2.7, 3.x and PyPy
+# Copyright (c) 2013 Leonard Ritter <contact@leonard-ritter.com>
+# 
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+# 
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+# 
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+
+"""
+glue.py is a single lightweight utility module expanding Python with
+powerful idioms for accessors, callsets, weak methods, enums, singletons and
+observable primitives that can be easily integrated with existing projects.
+"""
+
+from __future__ import (print_function, division, absolute_import)
+
+import collections
+import functools
+import gc
+import imp
+import inspect
+import sys
+import traceback
+import types
+import weakref
+import threading
+
+__all__ = [
+    'Undefined', 'enum', 'FunctionProxy', 'MethodProxy', 'callproxy',
+    'OrderedSet', 'StopCallableIteration', 'CallableSet', 'callset',
+    'SetPropertyEvent', 'genproperty', 'defproperty', 'autoinit', 'safecall',
+    'Dict', 'List', 'iterprops', 'Property', 'CallableQueue', 'callqueue',
+    'classproperty', 'ClassProperty', 'expect_empty', 'singleton',
+    'Singleton', 'History', 'deep_reload', 'Graph',
+    'clamp', 'mix', 'Named', 'Managed', 'main'
+]
+
+################################################################################
+
+# True if running in Python 3.0+
+ispython3 = sys.version_info >= (3, 0)
+
+# to be used for defaults where None is ambiguous
+if ispython3:
+    Undefined = type('Undefined', (), dict(
+        __bool__ = lambda self: False
+    ))()
+else:
+    Undefined = type('Undefined', (), dict(
+        __nonzero__ = lambda self: False
+    ))()
+
+################################################################################
+
+def enum(*sequential, **named):
+    """creates a new enum type of sequential or named enums and enhances it
+    with a reverse lookup method.
+    
+    enum can also be used as a class decorator.
+    """
+    if not named and len(sequential) == 1 and inspect.isclass(sequential[0]):
+        class_ = sequential[0]
+        keys = [k for k in class_.__dict__.keys() 
+            if not k.startswith('_') and not callable(getattr(class_,k))]
+        enums = dict((k,getattr(class_,k)) for k in keys)
+        reverse = dict((value, key) for key, value in enums.iteritems())
+        class_.keys = set(keys)
+        class_.reverse_dict = reverse
+        class_.count = len(reverse) 
+        return class_
+    
+    enums = dict(zip(sequential, range(len(sequential))), **named)
+    reverse = dict((value, key) for key, value in enums.iteritems())
+    enums['reverse_dict'] = reverse
+    enums['count'] = len(reverse)
+    return type('Enum', (), enums)
+
+def expect_empty(args):
+    """Simple tool function to complain about excess arguments or keyword
+    arguments passed, after all required elements have been popped.
+    
+    It is advisable to only make use of this check in baseclasses, where excess
+    keywords are not going to be processed by further baseclass constructors."""
+    if args:
+        if isinstance(args, dict):
+            msg = "Unexpected keyword(s): {}".format(args.keys())
+        else:
+            msg = "Unexpected argument(s): {}".format(args)
+        raise ValueError(msg)
+    
+def _make_safecall(func, default = Undefined):
+    @functools.wraps(func)
+    def wrapper(*args, **kargs):
+        try:
+            return func(*args, **kargs)
+        except:
+            traceback.print_exc()
+        return default
+    return wrapper
+
+def safecall(*args, **kwargs):
+    """
+    A decorator that wraps function func with an exception catcher. If an 
+    exception is raised, the traceback is printed and the function returns
+    a default value.     
+    
+    You can use the decorator with and without arguments. If the optional 
+    keyword argument "default" is omitted, the function will return Undefined.
+    """
+    if args:
+        return _make_safecall(*args, **kwargs)
+    else:
+        return functools.partial(_make_safecall, **kwargs)
+
+def main(func):
+    """
+    A decorator that calls function func if it's defined in the main module.
+    This can be used to easily designate a function as the main function:
+    
+    @main
+    def test():
+        print("main.")
+       
+    or
+    
+    def test():
+        print("main.")    
+    main(test)
+        
+    is equivalent to
+    
+    if __name__ == '__main__':
+        print("main.")
+    """
+    if func.__module__ == '__main__':
+        func()
+    
+################################################################################
+
+class FunctionProxy(object):
+    """a weak hashable and comparable function proxy."""
+
+    __slots__ = [
+        '_func',
+        '_hash',
+        '__weakref__',
+        '__call__'
+    ]
+    
+    def __init__(self, func, callback = None):
+        self._hash = hash(func)
+        self._func = weakref.ref(func, 
+            lambda r: callback(self) if callback else None)
+            
+    def __repr__(self):
+        return 'FunctionProxy({!r})'.format(self._func())
+            
+    def alive(self):
+        return True if self._func() else False
+            
+    if ispython3:
+        def __bool__(self):
+            return self.alive()
+    else:
+        def __nonzero__(self):
+            return self.alive()
+            
+    def __eq__(self, other):
+        return hash(self) == hash(other)
+    
+    def __ne__(self, other):
+        return hash(self) != hash(other)
+            
+    def get(self):
+        return self._func()
+            
+    def __hash__(self):
+        return self._hash
+        
+    def __call__(self, *args, **kwds):
+        func = self._func()
+        if not func:
+            return
+        return func(*args, **kwds)
+
+class MethodProxy(FunctionProxy):
+    """a weak hashable and comparable method proxy."""
+
+    __slots__ = [
+        '_self',
+    ]
+    
+    def __init__(self, func, callback = None):
+        FunctionProxy.__init__(self, func.__func__)
+        self._hash = hash(func)
+        self._self = weakref.ref(func.__self__, 
+            lambda r: callback(self) if callback else None)
+            
+    def __repr__(self):
+        return 'MethodProxy({!r}.{!r})'.format(self._self(),self._func())
+            
+    def alive(self):
+        return True if self._self() else False
+        
+    def __call__(self, *args, **kwds):
+        self_ = self._self()
+        if not self_:
+            return
+        FunctionProxy.__call__(self, self_, *args, **kwds)
+
+def callproxy(callable_, callback = None):
+    """turns any callable into a weak proxy. If the object associated with
+    the callable disappears, calling the proxy turns into a non-op.
+    
+    As with weakref.ref, callback will be invoked when the callable disappears.
+    """
+    assert callable(callable_), 'argument must be callable'
+    if inspect.ismethod(callable_):
+        return MethodProxy(callable_, callback)
+    else:
+        return FunctionProxy(callable_, callback)
+
+################################################################################
+
+def format_bytesize(num):
+    for x in ['bytes','KB','MB','GB']:
+        if num < 1024.0 and num > -1024.0:
+            return "%3.1f%s" % (num, x)
+        num /= 1024.0
+    return "%3.1f%s" % (num, 'TB')
+
+################################################################################
+
+class OrderedSet(collections.OrderedDict, collections.MutableSet):
+    """An ordered set where all write operations can be hooked."""
+    
+    hook = None
+    
+    class Event(object):
+        def __init__(self, set_, method, args, kwargs):
+            self.set = set_
+            self.method = method
+            self.args = args
+            self.kwargs = kwargs
+            
+        def __repr__(self):
+            return 'Event({!r},{!r},{!r},{!r})'.format(
+                self.set, self.method, self.args, self.kwargs)
+    
+    def _wrap(method): #@NoSelf
+        def wrapper(self, *args, **kwargs):
+            if self.hook:
+                self.hook(OrderedSet.Event(self, method, args, kwargs))
+            return method(self, *args, **kwargs)
+        return wrapper
+    
+    def __init__(self, *args, **kwargs):
+        super(OrderedSet, self).__init__(self)
+        self.update(*args, **kwargs)
+    
+    @_wrap
+    def update(self, *args, **kwargs):
+        if kwargs:
+            raise TypeError("update() takes no keyword arguments")
+        for s in args:
+            for e in s:
+                self.add(e)
+
+    @_wrap
+    def add(self, elem):
+        self[elem] = None
+
+    @_wrap
+    def discard(self, elem):
+        self.pop(elem, None)
+
+    def __hash__(self):
+        return object.__hash__(self)
+
+    def __le__(self, other):
+        return all(e in other for e in self)
+
+    def __lt__(self, other):
+        return self <= other and self != other
+
+    def __ge__(self, other):
+        return all(e in self for e in other)
+
+    def __gt__(self, other):
+        return self >= other and self != other
+
+    def __repr__(self):
+        return 'OrderedSet([%s])' % (', '.join(map(repr, self.keys())))
+
+    def __str__(self):
+        return '{%s}' % (', '.join(map(repr, self.keys())))
+    
+    def discard_weakrefs(self):
+        for e in self.keys():
+            if isinstance(e, weakref.ref) and (e() is None):
+                self.discard(e)
+            if isinstance(e, FunctionProxy) and not e.alive():
+                self.discard(e)
+
+    __ior__ = _wrap(collections.MutableSet.__ior__)
+    __isub__ = _wrap(collections.MutableSet.__isub__)
+    __iand__ = _wrap(collections.MutableSet.__iand__)
+    __ixor__ = _wrap(collections.MutableSet.__ixor__)
+
+    difference = property(lambda self: self.__sub__)
+    difference_update = property(lambda self: self.__isub__)
+    intersection = property(lambda self: self.__and__)
+    intersection_update = property(lambda self: self.__iand__)
+    issubset = property(lambda self: self.__le__)
+    issuperset = property(lambda self: self.__ge__)
+    symmetric_difference = property(lambda self: self.__xor__)
+    symmetric_difference_update = property(lambda self: self.__ixor__)
+    union = property(lambda self: self.__or__)
+
+################################################################################
+
+class StopCallableIteration(Exception):
+    """Aborts a callset iteration if raised while being called by a callset."""
+    pass
+
+class CallableSet(OrderedSet):
+    """A set of callables. Implements what is known as 'observable' pattern.
+    Enables decoupling of components by allowing to multiplex a call into many
+    calls. Can also be understood as a function pointer that points to many
+    functions, or a virtual method that is configured from the outside.
+    
+    When adding an observer's method to the set, use addweak() to wrap the
+    method in a callproxy to prevent the callable set from keeping your object
+    alive. When the object dies, the proxy will be removed from the set.  
+    """
+    
+    def __repr__(self):
+        return 'CallableSet([%s])' % (', '.join(map(repr, self.keys())))
+    
+    def addweak(self, callable_):
+        if callable_ in self:
+            return
+        self.add(callproxy(callable_, self.remove))
+    
+    def __call__(self, *args, **kargs):
+        try:
+            for callable_ in self:
+                callable_(*args,**kargs)
+        except StopCallableIteration:
+            pass
+        
+    def call_list(self, *args, **kargs):
+        try:
+            return [callable_(*args,**kargs) for callable_ in self]
+        except StopCallableIteration:
+            pass
+        
+    def call_parallel(self, *args, **kargs):
+        threads = []
+        for callable_ in self:
+            threads.append(threading.Thread(
+                target = callable_, args = args, kwargs = kargs))
+        for thread in threads:
+            thread.start()
+        for thread in threads:
+            thread.join()
+
+def callset(*args, **kargs):
+    """A shortcut for instantiating a CallableSet"""
+    return CallableSet(*args, **kargs)
+
+################################################################################
+
+class CallableSetBag(object):
+    """A collection (or so-called bag) of callable sets that can be accessed by
+    key or by attribute. The bag also contains an 'all' callset that allows to
+    subscribe to all callsets at once.
+    
+    Use callable set bags where you need to expose a range of observables
+    in your class, e.g. events in models that views can subscribe to.
+    """
+    
+    def __init__(self, *names):
+        self.__names = tuple(names)
+        all_events = callset()
+        events = dict(
+            all = all_events
+        )
+        for name in self.__names:
+            observers = callset()
+            observers.add(all_events)
+            events[name] = observers
+        self.__dict__.update(events)
+        
+    def __iter__(self):
+        return ((name,self.__dict__[name]) for name in self.__names)
+    
+    def __repr__(self):
+        return 'CallableSetBag{!r}'.format(self.__names) 
+    
+    def __getitem__(self, key):
+        return self.__dict__[key]
+
+def callsetbag(*names):
+    """A shortcut for instantiating a CallableSetBag"""
+    return CallableSetBag(*names)
+
+################################################################################
+
+def lazydecls(cls):
+    """A class decorator that searches a class for attributes carrying a
+    special '__lazydecl__' factory and uses it to instantiate the
+    attribute. This way, descriptors exposing a __lazydecl__ factory can
+    learn the class attribute they have been assigned to.
+    
+    The lazydecls() functionality is used to enable properties generated
+    with defproperty() to function."""
+    
+    items = dict(cls.__dict__).items()
+    for key, value in items:
+        if hasattr(value, '__lazydecl__'):
+            setattr(cls, key, value.__lazydecl__(cls, key))
+    return cls
+
+class LazyDecl(object):
+    """Descriptor implementing the __lazydecl__ operator."""
+    
+    def __get__(self, *args):
+        raise ValueError('class needs @lazydecls decorator')
+
+    def __set__(self, *args):
+        raise ValueError('class needs @lazydecls decorator')
+
+    def __delete__(self, *args):
+        raise ValueError('class needs @lazydecls decorator')
+
+    def __lazydecl__(self, cls, name):
+        # implement this function
+        pass
+        
+################################################################################
+
+class classproperty(property):
+    """A method decorator that allows to turn a classmethod into a class
+    property."""
+    
+    def __get__(self, cls, owner):
+        return classmethod(self.fget).__get__(None, owner)()
+
+################################################################################
+
+class SetPropertyEvent(object):
+    """An event sent by properties generated with defproperty() on change."""
+    
+    def __init__(self, instance, name, value):
+        self.instance = instance
+        self.name = name
+        self.value = value
+        
+    def __repr__(self):
+        return 'Event({!r},{!r},{!r})'.format(
+            self.instance, self.name, self.value)
+
+class Property(property):
+    """The descriptor of properties generated with defproperty()""" 
+    
+    def __repr__(self):
+        return 'Property({!r})'.format(self.name)
+    
+    def init(self, instance):
+        default = getattr(self, 'default', Undefined)
+        if default is Undefined:
+            return
+        if not callable(default):
+            return
+        setattr(instance, self.private, default())
+
+class ClassProperty(classproperty):
+    """An alternative descriptor for defproperty() that allows to generate
+    class properties."""
+    
+    def __repr__(self):
+        return 'ClassProperty({!r})'.format(self.name)
+        
+################################################################################
+
+if ispython3:
+    def _exec(s, globs):
+        exec(s, globs)
+else:
+    exec("""
+def _exec(s, globs):
+    exec s in globs
+""") 
+
+################################################################################
+
+_next_order = 0
+def genproperty(name, private, **options):
+    """A versatile and powerful property generator that generates code for
+    accessors typically needed in Python classes.
+    
+    This is the explicit function, which is rarely required. For more
+    documentation, see defproperty()."""
+    
+    global _next_order
+    privname = private    
+    readonly = options.pop('readonly', False)
+    default = options.pop('default', Undefined)
+    propcls = options.pop('class_', Property)
+    weak = options.pop('weak', False)
+    doc = options.pop('doc', None)
+    order = _next_order
+    _next_order += 1 
+
+    attribs = dict(
+        name = name,
+        private = privname,
+        readonly = readonly,
+        order = order
+    )
+    
+    if not (default is Undefined):
+        attribs.update(default=default)
+
+    s = 'def fget(self):\n'
+    if (default is Undefined) or callable(default):
+        s += '    value = getattr(self, privname)\n'
+    else:
+        s += '    value = getattr(self, privname, default)\n'
+    if weak:
+        attribs.update(weak=weak)
+        s += '    value = (value or None) and value()\n'
+    s += '    return value\n'
+    
+    globs = dict(globals())
+    globs.update(locals())
+    _exec(s, globs)
+    fget = globs['fget']
+    
+    ref = weakref.ref
+    
+    if not readonly:
+        rewrite = options.pop('rewrite', False)
+        types = options.pop('types', None)
+        validate = options.pop('validate', None)
+        sanitize = options.pop('sanitize', None)
+        hook = options.pop('hook',None)
+        if hook is None:
+            hook = options.pop('changing', None)
+        
+        s = 'def fset(self, value):\n'
+        
+        if not (sanitize is None):
+            attribs.update(sanitize = sanitize)
+            s += '    value = sanitize(value)\n'
+            
+        if not rewrite:
+            attribs.update(rewrite = rewrite)
+            s += '    if value == getattr(self, name, default):\n'
+            s += '        return\n'
+            
+        if not (types is None):
+            attribs.update(types = types)
+            s += '    if not isinstance(value, types):\n'
+            s += '        raise TypeError("type(s) {} expected, got {}".format(types, type(value)))\n'
+            
+        if not (validate is None):
+            attribs.update(validate = validate)
+            s += '    if not validate(value):\n'
+            s += '        raise ValueError("validation failed")\n'
+            
+        if not (hook is None):
+            attribs.update(hook = hook)
+            s += '    hook(SetPropertyEvent(self, name, value))\n'
+            
+        if weak:
+            s += '    value = None if value is None else ref(value)\n'
+            
+        s += '    setattr(self, privname, value)\n'
+
+        
+        globs = dict(globals())
+        globs.update(locals())        
+        _exec(s, globs)
+        fset = globs['fset']
+        
+        def fdel(self):
+            delattr(self, privname)
+    else:
+        fset = None
+        fdel = None
+        
+    if options:
+        raise ValueError('Unexpected keyword(s): {}'.format(options.keys()))
+
+    prop = propcls(fget=fget, fset=fset, fdel=fdel, doc=doc)
+    prop.__doc__ = doc
+    for name, value in attribs.items():
+        setattr(prop, name, value)
+        
+    return prop
+
+def defproperty(**options):
+    """Defines a versatile and powerful property and generates code for
+    accessors as needed. Classes using defproperty() require the @lazydecls
+    decorator and should call autoinit() during initialization.
+    
+    While properties are stored in classes independent of order of declaration,
+    properties generated with defproperty() expose an integer 'order' attribute
+    which can be used as key by inspectors to sort enumerated properties.
+      
+    In addition, following creation options are available, with the default
+    setting in brackets:
+    
+    * readonly (False): if True, the property is read-only / protected and
+    can not be set. This setting can be read from the properties 'readonly'
+    attribute.
+    
+    * default (Undefined): the default value for the property. If the value
+    is mutable or should be instantiated per class, a factory function can
+    be specified instead which will be used to initialize the instance
+    value. If you use a default factory, you must call autoinit() in
+    the classes __init__ method. If set, the setting can be read from the
+    properties 'default' attribute.
+    
+    * private (__<attribute name>): The name of the attribute in which
+    the property instance's value will be stored. This defaults to a private
+    attribute which can only be accessed from within the declared class. 
+    The private name can be read from the properties 'private' attribute.
+    
+    * hook (None): [only when readonly = False] A callable (which includes
+    callsets) that will be called right before the property is changed, with
+    an event of type SetPropertyEvent as only argument. Views can use hooks to
+    be notified when a models attribute changes, and refresh accordingly. 
+    If set, this setting can be read from the properties 'hook' attribute. 
+    'changing' is an alias for this option.
+    
+    * sanitize (None): [only when readonly = False] A function of the format
+    sanitize(value) -> value. If specified, will be called before rewrite, 
+    type and validate check. You can use types like int or float here. A
+    sanitizer does not raise errors, but attempts to turn a value into as
+    useful as possible before it is assigned, and equips a property with
+    "overload" capability. Typical use cases are type conversions and
+    clamping numerical values. If set, this setting can be read from the
+    properties 'sanitize' attribute.
+    
+    * types (None): [only when readonly = False] A single type or a tuple of
+    types that a value should be checked against. If the value does not match
+    any type, the setter will raise a TypeError. If set, this setting can be
+    read from the properties 'types' attribute.
+    
+    * validate (None): [only when readonly = False] A function of the format
+    validate(value) -> bool. If specified, will be called after the optional
+    type check, but before it is ultimately assigned. If validate returns
+    False, the setter will raise a ValueError. If set, this setting can be read
+    from the properties 'validate' attribute.
+    
+    * rewrite (False): [only when readonly = False] By default, the generated
+    setter will check if the new value equals the old one. If rewrite is True,
+    this check will be skipped. If set, this setting can be read from the
+    properties 'rewrite' attribute.
+    
+    * doc (None): The docstring for this property.
+    
+    * weak (False): if True, the stored value is transparently weakly
+    referenced. Useful for pointers that the class instance should not own.
+    Will not work with callables. If the referenced object dies, accessing
+    the attribute will return None. If set, the setting can be read from the
+    properties 'weak' attribute.
+    
+    * class_ (Property): the class used to instantiate the property. Rarely
+    required. Use ClassProperty if you want a class attribute.
+    """
+    
+    class LazyPropertyDecl(LazyDecl):
+        def __lazydecl__(self, class_, name): #@NoSelf
+            opts = dict(options)
+            private = opts.pop('private', Undefined)
+            if private is Undefined:
+                private = '__' + name
+                
+            if private.startswith('__'):
+                private = '_' + class_.__name__ + private
+            
+            return genproperty(name, private, **opts)
+    
+    return LazyPropertyDecl()
+
+def autoinit(cls = None, self = None, **kwargs):
+    """To be called in __init__ constructors of classes that make use of
+    defproperty(). Initializes (only) the properties within this class.
+    
+    The function may be called explicitly or implicitly, similar to super():
+    
+    autoinit(MyClass, self)
+    
+    If self is omitted, autoinit() will try to infer the 'self' attribute from
+    the callers local scope. If class is omitted, autoinit() infers the
+    function containing the code, assumes that it is a constructor and checks
+    which of self's classes contains it.
+    
+    For generic constructors that accept variable keyword arguments, the
+    dict can be passed on to autoinit, which pops all values from it for which
+    properties declared with defproperty() exist:
+    
+    @lazydecls
+    class O(object):
+        name = defproperty()
+    
+        def __init__(self, **kwargs):
+            expect_empty(autoinit(**kwargs))
+    
+    # will assign name, but complain about spam
+    o = O(name = 'test', spam = True)
+    
+    (Note: While it is convenient to use autoinit() without arguments, the
+    implementation may not work in all implementations of Python, as it
+    depends on interpreter internals. It has been tested with CPython 2.7,
+    3.2 and PyPy 1.9.)
+    """
+    
+    if (self and cls) is None:
+        callframe = inspect.currentframe().f_back
+        if self is None:
+            self = callframe.f_locals['self']
+        if cls is None:
+            code = callframe.f_code
+            for subcls in inspect.getmro(self.__class__):
+                method = subcls.__dict__.get('__init__', None)
+                if not method:
+                    continue
+                if method.__code__ == code:
+                    cls = subcls
+                    break
+            assert not (cls is None), 'could not find class for constructor'
+        
+    assert isinstance(self, cls)    
+    for name, prop in cls.__dict__.items():
+        if not isinstance(prop, Property):
+            continue
+        value = kwargs.pop(name, Undefined)
+        if value is Undefined:
+            prop.init(self)
+        else:
+            setattr(self, name, value)
+    
+    return kwargs
+
+def iterprops(obj):
+    """Iterate all properties defined by defproperty() in an object."""  
+    for name, prop in inspect.getmembers(obj.__class__): #@UnusedVariable
+        if not isinstance(prop, Property):
+            continue
+        yield prop
+
+################################################################################
+
+class Dict(dict):
+    """An observable drop-in replacement for Python dicts"""
+    
+    class Event(object):
+        def __init__(self, dict_, method, args, kwargs):
+            self.dict = dict_
+            self.method = method
+            self.args = args
+            self.kwargs = kwargs
+            
+        def __repr__(self):
+            return 'Event({!r},{!r},{!r},{!r})'.format(
+                self.dict, self.method, self.args, self.kwargs)
+                
+    hook = None
+    
+    def __init__(self, *args, **kwargs):
+        dict.__init__(self, *args, **kwargs)
+        
+    def _wrap(method): #@NoSelf
+        def wrapper(self, *args, **kwargs):
+            if self.hook:
+                self.hook(Dict.Event(self, method, args, kwargs))
+            return method(self, *args, **kwargs)
+        return wrapper
+    __delitem__ = _wrap(dict.__delitem__)
+    __setitem__ = _wrap(dict.__setitem__)
+    clear = _wrap(dict.clear)
+    pop = _wrap(dict.pop)
+    popitem = _wrap(dict.popitem)
+    setdefault = _wrap(dict.setdefault)
+    update =  _wrap(dict.update)
+    del _wrap
+
+################################################################################
+
+class List(list):
+    """An observable drop-in replacement for Python lists"""
+    
+    class Event(object):
+        def __init__(self, list_, method, args, kwargs):
+            self.list = list_
+            self.method = method
+            self.args = args
+            self.kwargs = kwargs
+            
+        def __repr__(self):
+            return 'Event({!r},{!r},{!r},{!r})'.format(
+                self.list, self.method, self.args, self.kwargs)
+                
+    hook = None
+    
+    def __init__(self, *args, **kwargs):
+        list.__init__(self, *args, **kwargs)
+        
+    def _wrap(method): #@NoSelf
+        def wrapper(self, *args, **kwargs):
+            if self.hook:
+                self.hook(List.Event(self, method, args, kwargs))
+            return method(self, *args, **kwargs)
+        return wrapper
+    __delitem__ = _wrap(list.__delitem__)
+    __setitem__ = _wrap(list.__setitem__)
+    if hasattr(list, '__delslice__'):
+        __delslice__ = _wrap(list.__delslice__)
+    if hasattr(list, '__setslice__'):
+        __setslice__ = _wrap(list.__setslice__)
+    pop = _wrap(list.pop)
+    append = _wrap(list.append)
+    insert = _wrap(list.insert)
+    __iadd__ = _wrap(list.__iadd__)
+    __imul__ = _wrap(list.__imul__)
+    remove = _wrap(list.remove)
+    sort = _wrap(list.sort)
+    extend = _wrap(list.extend)
+    reverse = _wrap(list.reverse)
+    del _wrap
+
+################################################################################
+
+class CallableQueue(object):
+    """a simple message queue that allows for callables to be propagated later"""
+    
+    def __init__(self):
+        self.clear()
+        
+    def clear(self):
+        self._todo = []
+        
+    def iterate(self):
+        while self._todo:
+            func,args,kwargs = self._todo.pop(0)
+            func(*args,**kwargs)
+            
+    def post(self, func, *args, **kwargs):
+        self._todo.append((func, args, kwargs))
+    
+def callqueue():
+    return CallableQueue()
+
+################################################################################
+
+def singleton(factory, **kwargs):
+    """
+    Can be used as decorator on a factory function. Should not be used as
+    class decorator, as you hide the class type reference.
+    
+    turns callable factory (usually the class) into a weak singleton 
+    provider. On first call, the generated function will call the factory
+    to produce a new singleton instance. Unless all references to the instance
+    are cleared, this function will always return the same object."""
+    
+    __instance = [None]
+    weak = kwargs.pop('weak', True)
+    if weak:
+        callback = kwargs.pop('callback', None)
+        def on_delete(ref):
+            __instance[0] = None
+            if callback:
+                callback(ref)
+        def instance():
+            if not __instance[0]:
+                instance = factory()
+                __instance[0] = weakref.ref(instance, on_delete)
+            return __instance[0]()
+    else:
+        def instance():
+            if not __instance[0]:
+                __instance[0] = factory()
+            return __instance[0]
+    expect_empty(kwargs)
+    return instance
+    
+def get_last_descendant(cls):
+    """return the last declared descendant"""
+    while cls.__subclasses__():
+        cls = cls.__subclasses__()[-1]
+    return cls    
+    
+class Singleton(type):
+    """
+    Singleton implemented as a metaclass to support inheritance. Use it like 
+    this:
+    
+    # Python 2.7
+    class MyClass(object):
+        __metaclass__ = Singleton
+    
+    # Python 3.x
+    class MyClass(metaclass=Singleton):
+        pass
+        
+    The singleton will make sure calls to baseclasses will also resolve to
+    inheriting classes. When the class has already been inherited and the
+    base class is instantiated, it will instantiate the last declared
+    descending class instead. Therefore, avoid inheritance trees, as you
+    will most likely end up with unintended results.
+    
+    Also, make sure all inheriting classes are declared before you instantiate
+    the singleton for the first time, or it will be re-instantiated.
+    """
+    
+    _instances = {}
+    
+    def __call__(cls, *args, **kwargs): #@NoSelf
+        # always take the last declared descendant
+        cls = get_last_descendant(cls)
+        result = cls._instances.get(cls, None)
+        if result:
+            return result
+        result = super(Singleton, cls).__call__(
+            *args, **kwargs)
+        cls._instances[cls] = result
+        return result
+    
+    def del_singleton(cls): #@NoSelf
+        cls = get_last_descendant(cls)
+        del cls._instances[cls]
+
+################################################################################
+
+@lazydecls
+class History(object):
+    """Converts event driven notifications to polling, e.g. for interfacing
+    with web clients which can not keep a standing connection or deferral
+    for delayed processing. If you wish to subscribe to a lot of callsets owned
+    by a single model class, consider grouping all callsets with a callsetbag,
+    then just pass the bags' 'all' callset to the History constructor.
+    """
+    
+    entries = defproperty(default = list)
+    
+    def __init__(self, *callablesets):
+        autoinit()
+        
+        for observers in callablesets:
+            observers.addweak(self.log_event)
+       
+    def pop_entries(self):
+        entries = list(self.entries)
+        self.entries = []
+        return entries
+    
+    def log_event(self, event):
+        self.entries.append(event)
+
+################################################################################
+
+def swap_classes(classmap):
+    """
+    Swaps classes using a old class -> new class dictionary passed as classmap. 
+    The function uses the gc module to enumerate all currently tracked 
+    instances of the old class, and exchanges their class attribute to the
+    new class. A list of objects that have been updated will be returned.
+    
+    This function is part of the deep reload functionality. 
+    """
+    classkeys = tuple(classmap.keys())
+
+    descendants = []
+    updated = []
+
+    for obj in gc.get_objects():
+        if inspect.isclass(obj):
+            if issubclass(obj, classkeys):
+                #if inspect.getmodule(obj) is module_:
+                #    continue
+                for cls in classkeys:
+                    if cls in obj.__bases__:
+                        descendants.append((obj, cls))
+        elif isinstance(obj, classkeys):
+            cls = obj.__class__
+            if cls in classkeys:
+                try:
+                    obj.__class__ = classmap[cls]
+                    updated.append(obj)
+                except TypeError:
+                    traceback.print_exc()
+    
+    if descendants:
+        # TODO: update inheriting classes
+        pass
+
+    return updated
+
+def iter_module_content(module_):
+    """
+    iterate the contents of a module for the purpose of tracking nested classes.
+    
+    This function is part of the deep reload functionality.
+    """
+    assert inspect.getmodule(module_) is module_
+    predicate = lambda x: inspect.getmodule(x) is module_
+    stack = [((),module_)]
+    walked = set()
+    while stack:
+        basename,namespace = stack.pop()
+        objid = id(namespace)
+        if objid in walked:
+            continue
+        walked.add(objid)
+        for name, obj in inspect.getmembers(namespace, predicate):
+            if inspect.isclass(obj) or inspect.isfunction(obj):
+                fullname = basename + (name,)
+                yield fullname, obj
+
+def resolve_namespace(module_, path):
+    """
+    Resolve a list of key names from module until the object is retrieved.
+    
+    This function is part of the deep reload functionality.
+    """
+    obj = module_
+    for name in path:
+        obj = getattr(obj, name)
+    return obj
+
+def deep_reload(module_):
+    """
+    Reloads module and updates all objects using classes from this module as
+    far as possible to match updated methods. It returns a list of objects
+    that have been updated.
+    """
+    content = list(iter_module_content(module_))
+    try:
+        imp.reload(module_)
+    except:
+        traceback.print_exc()
+        return
+    
+    classes = {}
+    for path, obj in content:
+        if inspect.isclass(obj):
+            newobj = resolve_namespace(module_, path)
+            if newobj is None:
+                continue
+            if obj is newobj:
+                print("object is same after reload?",obj,newobj)
+                continue
+            classes[obj] = newobj
+    
+    return swap_classes(classes)
+                
+################################################################################
+
+class Named(object):
+    """Many systems require objects to be distinguishable during debugging.
+    Countless objects therefore carry some kind of a name or id. This base class
+    facilitates this service and also supports assigning names by @lazydecl.
+    
+    If a class is decorated with lazydecl and has unnamed Named objects declared
+    at class level, the objects will be named after the attribute they have
+    been assigned to."""
+    
+    attributes = [
+        '_name',
+    ]
+    
+    def __init__(self):
+        self._name = ""
+        
+    def __repr__(self):
+        return '{0}({1})'.format(self.__class__.__name__, self._name or '?')
+
+    # auto-name from property if class is annotated with @lazydecls
+    def __lazydecl__(self, class_, name):
+        if self._name: return
+        self._name = name
+        return self 
+
+    @property
+    def name(self):
+        return self._name
+    @name.setter
+    def name(self, value):
+        self._name = value
+
+################################################################################
+
+class Managed(object):
+    """This class provides a simple wrapper for objects wrapping a C pointer
+    that needs to be freed when the object is collected. It also provides
+    a resolve function that allows to retrieve an existing wrapper for any
+    pointer.
+    
+    To use this class, inherit from it and assign _destroy_ptr(ptr) at class
+    level. _destroy_ptr should be a C function. You also need to assign _ffi
+    to the cffi instance you would like to be used to tag the weak reference.
+    """ 
+    
+    attributes = [
+        '_ptr',
+    ]
+    
+    # default implementation does nothing
+    _destroy_ptr = lambda ptr: None
+    
+    _ffi = None
+    _users = weakref.WeakValueDictionary()
+    
+    def __init__(self, ptr):
+        assert ptr
+        self._ptr = self._ffi.gc(ptr, self.__class__._destroy_ptr)
+        self._users[self._ptr] = self
+        
+    def destroy(self):
+        self._ptr = None
+        
+    @classmethod
+    def resolve(cls, ptr):
+        if not ptr: return None
+        return cls._users[ptr]
+    
+    @classmethod
+    def wrap(cls, ptr):
+        obj = cls._users.get(ptr, None)
+        if obj: return obj
+        obj = cls.__new__(cls)
+        obj._ptr = ptr
+        cls._users[obj._ptr] = obj
+        return obj
+                
+################################################################################
+                
+class Graph(object):
+    def __init__(self):
+        self.clear()
+        
+    def clear(self):
+        self._sorted = False
+        self._order = []
+        self.edges = {}
+        self.priorities = {}
+        
+    def invalidate(self):
+        self._sorted = False
+
+    def priority(self, node, prio):
+        """node with higher priority is first in order"""
+        self.priorities[node] = prio
+        self.invalidate()
+
+    def depends(self, target, *sources): 
+        """target depends on sources"""
+        sourceset = self.edges.setdefault(target, OrderedSet())        
+        for source in sources:
+            sourceset.add(source)
+        self.invalidate()
+            
+    def clear_sources(self, target):
+        self.edges.pop(target, None)
+        self.invalidate() 
+            
+    def sources(self, target):
+        return list(self.edges.get(target, []))
+
+    def targets(self, source):
+        result = OrderedSet()
+        for target,sources in self.edges.items():
+            if source in sources:
+                result.add(target)
+        return result
+
+    def calculate_order(self):
+        """traverses the graph and returns an ordered list of nodes"""
+        # find root nodes
+        roots = OrderedSet(self.edges.keys())
+        for target, sources in self.edges.iteritems():
+            roots.difference_update(sources)
+        if not roots:
+            return [] # cyclic dependency
+        
+        visited = OrderedSet()
+        order = OrderedSet()
+        stack = list(roots)
+        stack.sort(key=lambda k: self.priorities.get(k, 0))
+        
+        while stack:
+            node = stack[-1]
+            if node in visited:
+                order.add(node)
+                stack.pop()
+            else:
+                visited.add(node)
+                sources = list(self.edges.get(node, []))
+                sources.sort(key=lambda k: self.priorities.get(k, 0))
+                for source in sources:
+                    if source in visited:
+                        continue
+                    stack.append(source)
+        return list(order)
+    
+    def ensure_sorted(self):
+        if self._sorted:
+            return
+        self._order = self.calculate_order()
+        self._sorted = True
+
+    @property
+    def order(self):
+        self.ensure_sorted()
+        return self._order
+
+################################################################################
+
+def clamp(x, mn, mx):
+    if x != x: # nan
+        return mn
+    return min(max(x, mn), mx)
+
+def mix(a, b, x):
+    return a*(1-x) + b*x
+
+################################################################################
+
+def test_enums():
+    # enum as decorator
+    @enum
+    class MyEnum:
+        blue = 3
+        yellow = 5
+        green = 10
+        
+        @classmethod
+        def my_classfunc(cls):
+            print("i am a classfunc")
+            
+        def my_other_func(self):
+            pass
+        
+    assert 'blue' in MyEnum.keys
+    assert MyEnum.reverse_dict[5] == 'yellow'
+    assert MyEnum.count == 3
+
+def test_callable_proxy_set():
+    class C(object): 
+        A = []
+        
+        def func1(self):
+            self.A.append(1)
+        
+        def func2(self):
+            self.A.append(2)
+        
+    cset = CallableSet()
+    def scoped():
+        c = C() 
+        def func3():
+            C.A.append(3)
+        
+        assert callproxy(c.func1) == callproxy(c.func1)
+        assert callproxy(func3) == callproxy(func3)
+        assert callproxy(func3) == func3
+        assert callproxy(c.func1) == c.func1
+        assert callproxy(c.func1) != c.func2
+        assert callproxy(c.func1) != callproxy(c.func2)
+        assert callproxy(c.func1) != callproxy(func3)
+        assert callproxy(c.func2) != callproxy(func3)
+
+        assert hash(callproxy(c.func1)) != hash(callproxy(c.func2))
+        assert hash(callproxy(c.func1)) != hash(callproxy(func3))
+        assert hash(callproxy(c.func2)) != hash(callproxy(func3))
+            
+        cset.addweak(c.func1)
+        cset.addweak(c.func2)
+        cset.addweak(func3)
+        # should be ignored
+        cset.addweak(c.func1)
+        # should be ignored
+        cset.update([c.func2, c.func1]) 
+        assert c.func1 in cset
+        assert c.func2 in cset
+        assert func3 in cset
+        assert len(cset) == 3, len(cset)
+        cset()
+        assert c.A == [1,2,3], c.A
+        cset()
+        assert C.A == [1,2,3,1,2,3], C.A
+        
+        proxy = callproxy(func3)
+        assert proxy.alive()
+        assert proxy
+        
+        proxy2 = callproxy(c.func1)
+        assert proxy2.alive()
+        assert proxy2
+        
+        return proxy, proxy2
+    proxy, proxy2 = scoped()
+    gc.collect() # pypy fix    
+    assert not proxy.alive()
+    assert not proxy2.alive()
+    assert not proxy
+    assert not proxy2
+    cset()
+    assert C.A == [1,2,3,1,2,3], C.A
+    assert len(cset) == 0, len(cset)
+    
+def test_threaded_callset():
+    import time
+    cset = callset()
+    
+    things = []
+    cset.add(lambda k: things.append(k))
+    cset.add(lambda k: things.append(k+1))
+    cset.add(lambda k: things.append(k+2))
+    cset.add(time.sleep)
+    t = time.time()
+    cset.call_parallel(2)
+    assert time.time() - t >= 2
+    assert set(things) == set([2,3,4])
+    
+def test_property():
+    # base class
+    @lazydecls
+    class U(object):
+        ignore = defproperty()
+        
+        def __init__(self, **kwargs):
+            expect_empty(autoinit(**kwargs))
+             
+    # base class
+    @lazydecls
+    class B(U):
+        ignore2 = defproperty()
+        
+        def __init__(self, **kwargs):
+            U.__init__(self, **autoinit(**kwargs))
+    
+    # demo class
+    @lazydecls
+    class C(B):
+        # an observable shared by all properties in this class
+        event_changing = callset()
+        
+        # expand C by a property named "test" and autogenerate its accessors.
+        # By default, the internal name of the attribute will be "__test",
+        # but you can change this with the 'private' keyword.
+        # All attributes passed to the property can be read out from C.test
+        # later on by Views of this class to e.g. generate a GUI.
+        test = defproperty( 
+            # optional: if set, will check if values assigned to this property
+            # are instances of these types. Can be a single class or a tuple
+            # of classes.
+            types = str, 
+            # optional: an observer to be called when a new value is assigned
+            # to this property.
+            changing = event_changing,
+            # optional: a santizing function doing its best to translate the
+            # value to a valid form. 
+            sanitize = lambda v: v.upper(),
+            # optional: a validation function returning True if the value meets
+            # constraints. 
+            validate = lambda v: len(v) < 10,
+            # optional: the value this property will return by default.   
+            default = lambda: 'hi')
+        
+        def __init__(self, **kwargs):
+            B.__init__(self, **autoinit(**kwargs))
+            assert self.__test == 'hi'
+    
+    # create an instance of C for playing around
+    c = C(ignore = True, ignore2 = True)
+    assert c.ignore
+    assert c.ignore2
+    
+    assert sorted([p.name for p in iterprops(c)]) == ['ignore','ignore2','test']
+    
+    # an array that will hold all values assigned to monitored properties of c 
+    A = []
+    
+    # a handler function that will be called when properties of C change
+    def func(event):
+        A.append(event.value)
+    
+    # register our handler function with C
+    C.event_changing.add(func)
+    
+    # default value should be as specified
+    assert c.test == 'hi'
+    
+    # assign a new value that will be sanitized to uppercases
+    c.test = "hello"
+    
+    # c.test should now hold our sanitized value
+    assert c.test == "HELLO"
+    
+    # A should contain our santized value as well
+    assert A == ['HELLO']
+
+def test_dict():
+    k = {1:2,3:4}
+    
+    def handler(event):
+        event.method(k, *event.args, **event.kwargs) 
+    
+    d = Dict(k)
+    k[4] = 5
+    d.hook = CallableSet()
+    d.hook.add(handler)
+    del d[1]
+    d["test"] = "test"
+    assert not k is d
+    assert k == {'test':'test', 3:4, 4:5}
+
+def test_list():
+    k = [10]
+    
+    def handler(event):
+        event.method(k, *event.args, **event.kwargs)
+    
+    l = List(k)
+    l.hook = callset()
+    l.hook.add(handler)
+    del l[0]
+    l += [1,2,3]
+    l.append(5)
+    assert k == l, "{} != {}".format(k,l)
+
+def test_classproperty():
+    class classprop(object):
+        _instance = 10
+        
+        @classproperty
+        def instance(cls): #@NoSelf
+            return cls._instance
+    
+    assert classprop.instance is 10
+    classprop._instance = 15
+    assert classprop.instance is 15
+    
+def test_singleton():
+    class A(object):
+        pass
+    
+    @singleton
+    def make_a():
+        return A()
+    
+    a = make_a()
+    b = make_a()
+    c = make_a()
+    s = repr(a)
+    t = repr(b)
+    assert s == t and b is c
+    assert s == repr(make_a())
+    
+    del a
+    del b
+    del c
+    gc.collect()
+    
+    assert s != repr(make_a())
+    
+    class B(object):
+        pass
+    class C(B):
+        __metaclass__ = Singleton
+    class G(C):
+        pass
+    class D(C): # will override G
+        pass
+    class E(D):
+        pass
+    class F(E):
+        pass
+    
+    z = B()
+    a = C()
+    b = F()
+    c = E()
+    s = repr(a)
+    t = repr(b)
+    assert s == t and b is c
+    assert s == repr(C())
+    assert s != repr(B())
+    assert isinstance(a, F)
+    assert isinstance(z, B)
+
+def test_safecall():
+    @safecall
+    def noproblem(x,y):
+        return x/y
+    @safecall(default = 0)
+    def noproblem2(x,y):
+        return x/y
+    @safecall()
+    def noproblem3(x,y):
+        return x/y
+    
+    assert noproblem(1,2) == 0.5
+    assert noproblem(1,0) is Undefined
+    assert noproblem2(4,2) == 2
+    assert noproblem2(1,0) == 0
+    assert noproblem3(1,2) == 0.5
+    assert noproblem3(1,0) is Undefined
+
+if __name__ == '__main__':
+    test_enums()
+    test_callable_proxy_set()
+    test_threaded_callset()
+    test_property()
+    test_dict()
+    test_list()
+    test_classproperty()
+    test_singleton()
+    test_safecall()
+    print("OK.")

          
A => glue.py/setup.py +29 -0
@@ 0,0 1,29 @@ 
+
+import os
+from setuptools import setup
+
+def read(fname):
+    return open(os.path.join(os.path.dirname(__file__), fname)).read()
+
+setup(
+    name = "glue.py",
+    version = "0.6",
+    author = "Leonard Ritter",
+    author_email = "contact@leonard-ritter.com",
+    description = ("The agnostic model/view toolshed"),
+    license = "MIT",
+    keywords = "python glue mvc observable idioms lightweight",
+    url = "https://bitbucket.org/duangle/glue.py",
+    py_modules=['glue'],
+    long_description=read('README.text'),
+    classifiers=[
+        "Development Status :: 5 - Production/Stable",
+        "Intended Audience :: Developers",
+        "Programming Language :: Python :: 2.7",
+        "Programming Language :: Python :: 3",
+        "Programming Language :: Python :: Implementation :: CPython",
+        "Programming Language :: Python :: Implementation :: PyPy",
+        "Topic :: Software Development :: Libraries",
+        "License :: OSI Approved :: MIT License",
+    ],
+)

          
A => liminal_legacy/.hg_archival.txt +6 -0
@@ 0,0 1,6 @@ 
+repo: 123b51019c6e65f82cc6267647cc56fe746419e4
+node: 36e8bfde49b262ac7195fe5e56e94395bb73a654
+branch: default
+latesttag: null
+latesttagdistance: 849
+changessincelatesttag: 852

          
A => liminal_legacy/.hgignore +39 -0
@@ 0,0 1,39 @@ 
+syntax: glob
+
+.sconsign.dblite
+.settings
+*.marsh
+*.egg-info
+*.pyc
+*.json
+*.blend1
+*.blend2
+*.blendc
+screenshot*.png
+batchgraph*.png
+*.avi
+*.glc
+*.mp4
+*.p3d
+debug.txt
+debuglog.txt
+__pycache__
+*.orig
+*.pyd
+*.soil
+*.cdef
+*.bgeconf
+*.so
+data.dat
+.project
+.pydevproject
+.liminal*.db
+.sass-cache
+
+syntax: regexp
+
+^build
+^doc/build
+^dist
+^py2exe/build
+^py2exe/dist

          
A => liminal_legacy/LICENSE +26 -0
@@ 0,0 1,26 @@ 
+
+Except when otherwise stated (look for LICENSE files in directories or
+information at the beginning of each file) all software and
+documentation is licensed as follows: 
+
+    The MIT License
+
+    Permission is hereby granted, free of charge, to any person 
+    obtaining a copy of this software and associated documentation 
+    files (the "Software"), to deal in the Software without 
+    restriction, including without limitation the rights to use, 
+    copy, modify, merge, publish, distribute, sublicense, and/or 
+    sell copies of the Software, and to permit persons to whom the 
+    Software is furnished to do so, subject to the following conditions:
+
+    The above copyright notice and this permission notice shall be included 
+    in all copies or substantial portions of the Software.
+
+    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 
+    OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
+    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 
+    THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
+    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 
+    FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 
+    DEALINGS IN THE SOFTWARE.
+

          
A => liminal_legacy/TODO +33 -0
@@ 0,0 1,33 @@ 
+
+Optimization ideas based on "Porting Source to Linux - Valve's Lessons Learned":
+https://developer.nvidia.com/sites/default/files/akamai/gamedev/docs/Porting%20Source%20to%20Linux.pdf
+
+* Replace VAOs with directly loading the buffers, as it's faster.
+  (see "Vertex Attribs – Alternative #1")
+
+* See if we can get RAD's Telemetry going
+
+* Use index buffers where possible
+
+* avoid binding objects, use EXT_direct_state_access
+
+* control vsync with EXT_swap_interval / EXT_swap_control_tear
+  ( http://www.opengl.org/wiki/Swap_Interval )
+
+* use NVX_gpu_memory_info / GL_ATI_meminfo
+
+* store texture settings with samplers, ARB_sampler_objects
+
+* avoid glGet*, glGetError, GL functions that return a value
+
+* use ARB_vertex_attrib_binding
+
+* use glBindMultiTextureEXT instead of glActiveTexture/glBindTexture
+
+* on-GPU texture compression: https://code.google.com/p/nvidia-texture-tools/
+  (more compression stuff here: http://developer.download.nvidia.com/SDK/10/opengl/samples.html)
+  
+* copy rects: EXT_copy_texture, stretch rects: NV_draw_texture
+
+* stretch rect; MSAA blit scaled: EXT_framebuffer_multisample_blit_scaled
+

          
A => liminal_legacy/doc/Makefile +153 -0
@@ 0,0 1,153 @@ 
+# Makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line.
+SPHINXOPTS    =
+SPHINXBUILD   = sphinx-build
+PAPER         =
+BUILDDIR      = build
+
+# Internal variables.
+PAPEROPT_a4     = -D latex_paper_size=a4
+PAPEROPT_letter = -D latex_paper_size=letter
+ALLSPHINXOPTS   = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source
+# the i18n builder cannot share the environment and doctrees with the others
+I18NSPHINXOPTS  = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source
+
+.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
+
+help:
+	@echo "Please use \`make <target>' where <target> is one of"
+	@echo "  html       to make standalone HTML files"
+	@echo "  dirhtml    to make HTML files named index.html in directories"
+	@echo "  singlehtml to make a single large HTML file"
+	@echo "  pickle     to make pickle files"
+	@echo "  json       to make JSON files"
+	@echo "  htmlhelp   to make HTML files and a HTML help project"
+	@echo "  qthelp     to make HTML files and a qthelp project"
+	@echo "  devhelp    to make HTML files and a Devhelp project"
+	@echo "  epub       to make an epub"
+	@echo "  latex      to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
+	@echo "  latexpdf   to make LaTeX files and run them through pdflatex"
+	@echo "  text       to make text files"
+	@echo "  man        to make manual pages"
+	@echo "  texinfo    to make Texinfo files"
+	@echo "  info       to make Texinfo files and run them through makeinfo"
+	@echo "  gettext    to make PO message catalogs"
+	@echo "  changes    to make an overview of all changed/added/deprecated items"
+	@echo "  linkcheck  to check all external links for integrity"
+	@echo "  doctest    to run all doctests embedded in the documentation (if enabled)"
+
+clean:
+	-rm -rf $(BUILDDIR)/*
+
+html:
+	$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
+	@echo
+	@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
+
+dirhtml:
+	$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
+	@echo
+	@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
+
+singlehtml:
+	$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
+	@echo
+	@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
+
+pickle:
+	$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
+	@echo
+	@echo "Build finished; now you can process the pickle files."
+
+json:
+	$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
+	@echo
+	@echo "Build finished; now you can process the JSON files."
+
+htmlhelp:
+	$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
+	@echo
+	@echo "Build finished; now you can run HTML Help Workshop with the" \
+	      ".hhp project file in $(BUILDDIR)/htmlhelp."
+
+qthelp:
+	$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
+	@echo
+	@echo "Build finished; now you can run "qcollectiongenerator" with the" \
+	      ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
+	@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Beige.qhcp"
+	@echo "To view the help file:"
+	@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Beige.qhc"
+
+devhelp:
+	$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
+	@echo
+	@echo "Build finished."
+	@echo "To view the help file:"
+	@echo "# mkdir -p $$HOME/.local/share/devhelp/Beige"
+	@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Beige"
+	@echo "# devhelp"
+
+epub:
+	$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
+	@echo
+	@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
+
+latex:
+	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+	@echo
+	@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
+	@echo "Run \`make' in that directory to run these through (pdf)latex" \
+	      "(use \`make latexpdf' here to do that automatically)."
+
+latexpdf:
+	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
+	@echo "Running LaTeX files through pdflatex..."
+	$(MAKE) -C $(BUILDDIR)/latex all-pdf
+	@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
+
+text:
+	$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
+	@echo
+	@echo "Build finished. The text files are in $(BUILDDIR)/text."
+
+man:
+	$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
+	@echo
+	@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
+
+texinfo:
+	$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
+	@echo
+	@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
+	@echo "Run \`make' in that directory to run these through makeinfo" \
+	      "(use \`make info' here to do that automatically)."
+
+info:
+	$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
+	@echo "Running Texinfo files through makeinfo..."
+	make -C $(BUILDDIR)/texinfo info
+	@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
+
+gettext:
+	$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
+	@echo
+	@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
+
+changes:
+	$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
+	@echo
+	@echo "The overview file is in $(BUILDDIR)/changes."
+
+linkcheck:
+	$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
+	@echo
+	@echo "Link check complete; look for any errors in the above output " \
+	      "or in $(BUILDDIR)/linkcheck/output.txt."
+
+doctest:
+	$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
+	@echo "Testing of doctests in the sources finished, look at the " \
+	      "results in $(BUILDDIR)/doctest/output.txt."

          
A => liminal_legacy/doc/articles/linkdemo.py +262 -0
@@ 0,0 1,262 @@ 
+
+"""
+
+The Liminal RNode System - Scheduling Renderjobs In A Crazy World
+by Leonard Ritter, Duangle GbR (2014.4.11)
+
+
+
+Structuring rendering in a 3D application to allow for drastical alterations
+    in pipeline ordering isn't easy; you usually end up wanting all kinds of
+    permutations, like "i want _this_ actor with _this_ mesh, but this time into
+    _this_ framebuffer, but with the same camera data; screw that; we need
+    the same data in two different rendertargets, oh and I forgot we need
+    splitscreen, so all this needs to be split into four viewports - except
+    for this object, which needs to be on top of it all."
+
+...and the graphics engine coder works his ass off for two more weeks; usually
+these changes ripple through the entire codebase; starting at providing
+basic support at the engine level, over to the game programmer, who then has
+to make use of the new flags and refactor the rendering setup.
+
+"""
+
+# so here's how to setup scenes in our Liminal engine now;
+# it's a bit like linking generators and effects in a modular sequencer,
+# only that it works on packets (aka renderjobs) instead of streams of
+# audio impulses.
+
+# I haven't seen a game engine which organizes its renderpath like this yet, 
+# so I'm pretty excited to demo this and get everyone's input on what I
+# think is a pretty lean and elegant way to organize 3D rendering in a game.
+
+
+# first, generate a default, uv mapped cube mesh with normals
+mesh = generate_cube_mesh()
+mesh.name = "cube"
+
+# setup a deferred material for the mesh
+material = GMaterial.new()
+material.name = "CubeMaterial"
+material.use_backface_culling = True
+material.use_shadow = True
+material.albedo = vec3(1.0,1.0,1.0)
+material.metallic_factor = 0.0
+material.roughness_factor = 1.0
+material.add_shader_texture('albedo_map', noise1_texture)
+material.add_shader_texture('height_map', noise1_texture)
+material.add_shader_texture('gloss_map', noise1_texture)
+material.add_shader_texture('glow_map', zero_texture())
+
+# create a new actor to give the mesh a location
+# the actor has no conception of anything else; just an orientation in space
+cube2 = Actor()
+cube2.name = 'cube2'
+# give it a physical body for collision testing
+cube2.body = GeomBody.new(ws.world, Box.new(ws.space, vec3(64,64,1)))
+cube2.body.kinematic = True
+# apply position and size
+cube2.position = vec3(0,0,-10)
+cube2.scale = vec3(32.0,32.0,0.5)
+
+# now concatenate our renderjob:
+# we want this mesh with this material at this actor's location,
+# so cube2 now depends on material, which depends on mesh
+cube2 << material << mesh
+
+# the order of assignments is only interesting in relation to the graph,
+# the only requirement is that the mesh is the a source, as meshes are
+# the only nodes that generate renderjobs; other nodes only augment them.
+#
+# this would be equally valid, and a good setup for when many actors
+# share the same material:
+material << cube2 << mesh
+
+# now setup an empty vizgroup which we use to bundle a group of objects,
+# possibly to control visibility later. This would be kind of like a "scene"
+vizgroup = RNode()
+vizgroup.name = 'scene' 
+# make vizgroup depend on cube2
+vizgroup << cube2
+
+# The RNode is the basic building block for the render graph; materials, meshes, 
+# actors, framebuffers, etc. and a lot of other default resources already
+# implement the RNode interface so they can be linked; But it's pretty easy
+# to write your own RNodes, or use a RNode as proxy to re-use another RNode in
+# two different unrelated paths.
+
+# get the singleton for the deferred renderer's resources, which is also
+# an RNode
+grm = GRenderManager()
+
+# link the vizgroup to the framebuffer for the deferred "scene" -
+grm.scene_framebuffer << vizgroup
+
+# now the last thing we need to complete the job is a camera to actually 
+# specify where the scene is in relation to the viewer; 
+camera = DebugCamera(lock_up=True)
+camera.name = 'camera'
+camera.far = 300.0
+camera.fov = 90.0
+# give the camera a collision box
+camera.body = GeomBody.new(ws.world, Box.new(ws.space, vec3(1,1,1)))
+camera.body.kinematic = True
+# and position it
+camera.look_at(vec3(0,-70.0,0), vec3(0,0,0), vec3(0,0,1))
+            
+# all resources the deferred renderer has set up are set, except for the view
+# data, so let's just link the entire grm through it and we're done; the
+# scene is on screen.
+#
+# retrieve render manager singleton
+rm = RenderManager()
+# complete the chain; batch is the root node - now the renderer can 'see'
+# the content.
+rm.batch << camera << grm
+
+# For debugging, I have written a small 90 line function to have graphviz
+# draw a map of the graph, so I can see at any point what the pipeline looks
+# like 
+
+"""
+
+"""
+
+# the declaration for RNodes is only 70 lines, and writing new RNodes is simple;
+# here is an RNode that changes the renderpass of all jobs going through it:
+class Renderpass(RNode):
+    def __init__(self, renderpass):
+        RNode.__init__(self)
+        self.renderpass = renderpass
+        
+    def process(self, batch, jobs):
+        RNode.process(self, batch, jobs)
+        for job in jobs:
+            # we could also insert new orders into the jobs array here
+            # but let's just alter the renderpass
+            job.key.renderpass = self.renderpass
+
+# rnodes are only traversed on graph change, not per frame, and the work can
+# be done in a thread, as the renderer isn't required for processing.
+#
+# this is how the renderbatch then generates all jobs while traversing
+# the graph:
+
+def render_nodes(self):
+    # this could be done in a more conside way in python,
+    # but i wanted to keep the implementation parallel to a possible
+    # future C implementation; this one doesn't need any hash tables
+    # or binary searches, just arrays.
+    
+    # get a new selection id
+    RNode.NODE_ID += 1
+    node_id = RNode.NODE_ID 
+    
+    # array of nodes in topological order
+    nodes = []
+    # how many times each node's content is referenced
+    noderefs = []
+    
+    # recursive visitor function
+    def visit_node(node):
+        # if node has already been seen, skip 
+        if node._node_id == node_id: return
+        # visit children first
+        for n in node.sources:
+            visit_node(n)
+            # increase noderefs for that node
+            noderefs[n._node_index] += 1
+        # assign the node's order index
+        node._node_index = len(nodes)
+        # update the select id to mark the node as visited
+        node._node_id = node_id
+        # append to topologically sorted array
+        nodes.append(node)
+        # and initialize the refcount for this node
+        noderefs.append(0)
+        
+    # start at batch, which is the root
+    visit_node(self)
+    
+    # debug out: print order in which nodes are processed, (which
+    # is not the order in which they are rendered, btw)
+    for i,n in enumerate(nodes):
+        print('#{}: {}'.format(i,n))
+        
+    # array of each node's jobs
+    all_jobs = []
+    # linearly go through node in sorted order
+    for k,node in enumerate(nodes):
+        # list of jobs that this node will receive
+        jobs = []
+        # for all inputs of this node
+        for source in node.sources:
+            # retrieve the jobs that the predecessor has
+            src_index = source._node_index
+            src_jobs = all_jobs[src_index]
+            # make sure we have at least one reference left
+            assert noderefs[src_index] > 0
+            # if this is not the last reference
+            if noderefs[src_index] > 1:
+                # make a copy of the jobs and append
+                for job in src_jobs:
+                    jobs.append(self.copy_job(job))
+            else:
+                # last reference: they terk er jerbs
+                for job in src_jobs:
+                    jobs.append(job)
+                all_jobs[src_index] = None
+            # and decrease reference
+            noderefs[src_index] -= 1
+        
+        # process the jobs
+        try:
+            node.process(self, jobs)
+        except:
+            print(node)
+            raise         
+        # and append to job list       
+        all_jobs.append(jobs)
+        
+    # as jobs are already appended to the renderbatch on create/copy,
+    # we are already done; the batch can now be sorted and is ready
+    # for rendering.
+
+# And, just for completion, this is the basic RNode class declaration:
+
+class RNode(Named):
+    # selection id counter
+    NODE_ID = 0 
+    
+    def __init__(self):
+        Named.__init__(self)
+        # set of input nodes
+        self._sources = set()
+        # variables used during graph traversion
+        self._node_index = 0
+        self._node_id = 0
+        
+    # this function does a number on the jobs; any operation is OK:
+    # alteration, duplication, deletion, etc. Change of order has no effect
+    # on anything.
+    def process(self, batch, jobs):
+        pass
+    
+    @property
+    def sources(self):
+        return self._sources
+        
+    # overloaded << operator to provide linking syntax sugar
+    def __lshift__(self, other):
+        assert self.name, '{} is unnamed'.format(self)
+        if isinstance(other, collections.Iterable):
+            for node in other:
+                assert node.name, '{} is unnamed'.format(other)
+            self.sources.update(other)
+            return other
+        else:
+            assert other.name, '{} is unnamed'.format(other)
+            self.sources.add(other)
+            return other    
+
+# That's it. Thanks for reading :)

          
A => liminal_legacy/doc/articles/split-scheme.txt +146 -0
@@ 0,0 1,146 @@ 
+           An Inversible Split Scheme for Cascading Shadow Maps
+
+                           by Leonard Ritter
+                              Duangle GbR
+                              
+                        First revision: 2013/12/18
+
+
+DISCLAIMER: This is original research and has not been verified by a
+second or third party yet.
+
+
+This is a proposal for a new function for the practical split 
+scheme[1] for shadow map cascades by Zhang et al. The new 
+function can be inverted in a shader to easily project 
+distance z  back to layer index i. An intuitive replacement 
+for the weighing factor sigma is proposed as well.
+
+
+In the original article[1], a split scheme function was suggested to
+find good boundaries for individual shadow map cascades, so that
+given a layer index i, the optimal depth z for that index could
+be derived; given a normal scalar x which is retrieved via
+
+                                         i
+                                x = -----------
+                                    layer_count
+
+and a sigma constant S in the range 0..1, which would allow the
+user interpolate linearly between an equidistant depth distribution
+and an ideal exponential one to bias the resolution distribution,
+the original split scheme equation would be
+
+                          f
+                  z = pow(-, x) n S + ((f - n) x + n) (1 - S) = f(x)
+                          n
+
+n and f designate the distance of the near and far planes respectively.
+
+Unfortunately this function can't be inverted. With this solution,
+it is necessary to precompute and pass the layer boundaries to the 
+pixel shader, which then performs a conditional range check
+for each fragment.
+
+
+The New Split Scheme
+====================
+
+Through experimentation and some guesswork, I found an alternative
+way to bias the distribution, which is purely exponential and
+has no additive polynomial component, so inversion is trivial.
+
+This bias effectively projects the near and far distances to a
+slope further up the exponential curve, which dampens the initial
+slow momentum and causes the function to approach a more linear
+scaling.
+
+The original sigma constant S is squared so that C = sqrt(S),
+and the split scheme function is altered so that 
+
+                 f                     f
+            z = (- + n - f) (pow(-------------, x) - 1.0) + n = f(x)
+                 C               (n - f) C + f
+
+At the original proposed default of S = 0.5, the function starts
+with near identical momentum, but converges marginally sooner
+towards f; across all values of S, the first half of the function
+distributes very similarly, so this function can easily be used 
+as a drop-in replacement.
+
+
+Analysis
+========
+
+http://i.imgur.com/9OeUMBz.png
+
+The above picture shows the distribution for sigma = 0.5; the
+green and yellow curves show the boundaries at sigma 0.0 and 1.0,
+which are the same for both old and new functions.
+The purple curve is the original split scheme, the blue curve
+is the modified one.
+
+
+Shader Usage
+============
+
+In the shader, the layer index can now be retrieved by resolving x from a
+distance z and multiplying it with layer_count to retrieve the interpolated
+index of the layer to be retrieved.
+
+To calculate x from z, we inverse the new split scheme function so that
+
+                (z - far) C + far                  far
+       x = log(--------------------) / log(--------------------) = f(z)
+               (near - far) C + far        (near - far) C + far
+
+For optimization, the inner factors and the second log term can be stored in
+three constant variables U, V, W:
+
+                           d = far * (1.0 - C) + near * C
+
+                                         C
+                                     U = -
+                                         d
+
+                                   far - far * C
+                               V = -------------
+                                         d
+
+                                                                far
+                                 W = layer_count * log(2) / log(---)
+                                                                 d
+
+With these optimizations, the resolve reduces to
+
+                              i = log2(z U + V) W
+
+
+Future Work
+===========
+
+I did not find the original weighing parameter sigma to be particularly
+easy to understand and apply. In practice, I found myself aiming to get
+an optimal balance between resolution and the range of the first cascade
+layer by guessing values for sigma; There was no clear relationship
+between the parameter and its implications.
+
+Ideally, I would prefer a new balancing variable z1 instead of sigma, which
+defines the distance at which the first shadow map layer ends, and is
+used to calculate the bias C.
+
+Unfortunately, I could not find a way to solve the equation towards C,
+so I leave this for further exploration. Until a simpler method can be found,
+bisecting towards C would be the nearest choice; A routine would 
+methodically attempt to solve the split scheme function by adjusting C until
+z = z1 for x = 1 / layer_count.
+
+
+References
+==========
+
+[1] Parallel-Split Shadow Maps on Programmable GPUs
+    http://http.developer.nvidia.com/GPUGems3/gpugems3_ch10.html
+
+
+Thanks to Sean Barrett and Fabian Giesen for help and suggestions.

          
A => liminal_legacy/doc/make.bat +190 -0
@@ 0,0 1,190 @@ 
+@ECHO OFF
+
+REM Command file for Sphinx documentation
+
+if "%SPHINXBUILD%" == "" (
+	set SPHINXBUILD=sphinx-build
+)
+set BUILDDIR=build
+set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% source
+set I18NSPHINXOPTS=%SPHINXOPTS% source
+if NOT "%PAPER%" == "" (
+	set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
+	set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS%
+)
+
+if "%1" == "" goto help
+
+if "%1" == "help" (
+	:help
+	echo.Please use `make ^<target^>` where ^<target^> is one of
+	echo.  html       to make standalone HTML files
+	echo.  dirhtml    to make HTML files named index.html in directories
+	echo.  singlehtml to make a single large HTML file
+	echo.  pickle     to make pickle files
+	echo.  json       to make JSON files
+	echo.  htmlhelp   to make HTML files and a HTML help project
+	echo.  qthelp     to make HTML files and a qthelp project
+	echo.  devhelp    to make HTML files and a Devhelp project
+	echo.  epub       to make an epub
+	echo.  latex      to make LaTeX files, you can set PAPER=a4 or PAPER=letter
+	echo.  text       to make text files
+	echo.  man        to make manual pages
+	echo.  texinfo    to make Texinfo files
+	echo.  gettext    to make PO message catalogs
+	echo.  changes    to make an overview over all changed/added/deprecated items
+	echo.  linkcheck  to check all external links for integrity
+	echo.  doctest    to run all doctests embedded in the documentation if enabled
+	goto end
+)
+
+if "%1" == "clean" (
+	for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
+	del /q /s %BUILDDIR%\*
+	goto end
+)
+
+if "%1" == "html" (
+	%SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.Build finished. The HTML pages are in %BUILDDIR%/html.
+	goto end
+)
+
+if "%1" == "dirhtml" (
+	%SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
+	goto end
+)
+
+if "%1" == "singlehtml" (
+	%SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.
+	goto end
+)
+
+if "%1" == "pickle" (
+	%SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.Build finished; now you can process the pickle files.
+	goto end
+)
+
+if "%1" == "json" (
+	%SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.Build finished; now you can process the JSON files.
+	goto end
+)
+
+if "%1" == "htmlhelp" (
+	%SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.Build finished; now you can run HTML Help Workshop with the ^
+.hhp project file in %BUILDDIR%/htmlhelp.
+	goto end
+)
+
+if "%1" == "qthelp" (
+	%SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.Build finished; now you can run "qcollectiongenerator" with the ^
+.qhcp project file in %BUILDDIR%/qthelp, like this:
+	echo.^> qcollectiongenerator %BUILDDIR%\qthelp\Beige.qhcp
+	echo.To view the help file:
+	echo.^> assistant -collectionFile %BUILDDIR%\qthelp\Beige.ghc
+	goto end
+)
+
+if "%1" == "devhelp" (
+	%SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.Build finished.
+	goto end
+)
+
+if "%1" == "epub" (
+	%SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.Build finished. The epub file is in %BUILDDIR%/epub.
+	goto end
+)
+
+if "%1" == "latex" (
+	%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
+	goto end
+)
+
+if "%1" == "text" (
+	%SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.Build finished. The text files are in %BUILDDIR%/text.
+	goto end
+)
+
+if "%1" == "man" (
+	%SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.Build finished. The manual pages are in %BUILDDIR%/man.
+	goto end
+)
+
+if "%1" == "texinfo" (
+	%SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo.
+	goto end
+)
+
+if "%1" == "gettext" (
+	%SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.Build finished. The message catalogs are in %BUILDDIR%/locale.
+	goto end
+)
+
+if "%1" == "changes" (
+	%SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.The overview file is in %BUILDDIR%/changes.
+	goto end
+)
+
+if "%1" == "linkcheck" (
+	%SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.Link check complete; look for any errors in the above output ^
+or in %BUILDDIR%/linkcheck/output.txt.
+	goto end
+)
+
+if "%1" == "doctest" (
+	%SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
+	if errorlevel 1 exit /b 1
+	echo.
+	echo.Testing of doctests in the sources finished, look at the ^
+results in %BUILDDIR%/doctest/output.txt.
+	goto end
+)
+
+:end

          
A => liminal_legacy/doc/source/_static/__donotdelete__ +0 -0

        
A => liminal_legacy/doc/source/_templates/__donotdelete__ +0 -0

        
A => liminal_legacy/doc/source/conf.py +242 -0
@@ 0,0 1,242 @@ 
+# -*- coding: utf-8 -*-
+#
+# Beige documentation build configuration file, created by
+# sphinx-quickstart on Thu Jan  3 13:01:37 2013.
+#
+# This file is execfile()d with the current directory set to its containing dir.
+#
+# Note that not all possible configuration values are present in this
+# autogenerated file.
+#
+# All configuration values have a default; values that are commented out
+# serve to show the default.
+
+import sys, os
+
+# If extensions (or modules to document with autodoc) are in another directory,
+# add these directories to sys.path here. If the directory is relative to the
+# documentation root, use os.path.abspath to make it absolute, like shown here.
+sys.path.insert(0, os.path.abspath('..'))
+
+# -- General configuration -----------------------------------------------------
+
+# If your documentation needs a minimal Sphinx version, state it here.
+#needs_sphinx = '1.0'
+
+# Add any Sphinx extension module names here, as strings. They can be extensions
+# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
+extensions = ['sphinx.ext.autodoc', 'sphinx.ext.todo', 'sphinx.ext.coverage', 'sphinx.ext.pngmath', 'sphinx.ext.ifconfig', 'sphinx.ext.viewcode']
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ['_templates']
+
+# The suffix of source filenames.
+source_suffix = '.txt'
+
+# The encoding of source files.
+#source_encoding = 'utf-8-sig'
+
+# The master toctree document.
+master_doc = 'index'
+
+# General information about the project.
+project = u'Beige'
+copyright = u'2013, Leonard Ritter'
+
+# The version info for the project you're documenting, acts as replacement for
+# |version| and |release|, also used in various other places throughout the
+# built documents.
+#
+# The short X.Y version.
+version = '0.1'
+# The full version, including alpha/beta/rc tags.
+release = '0.1'
+
+# The language for content autogenerated by Sphinx. Refer to documentation
+# for a list of supported languages.
+#language = None
+
+# There are two options for replacing |today|: either, you set today to some
+# non-false value, then it is used:
+#today = ''
+# Else, today_fmt is used as the format for a strftime call.
+#today_fmt = '%B %d, %Y'
+
+# List of patterns, relative to source directory, that match files and
+# directories to ignore when looking for source files.
+exclude_patterns = []
+
+# The reST default role (used for this markup: `text`) to use for all documents.
+#default_role = None
+
+# If true, '()' will be appended to :func: etc. cross-reference text.
+#add_function_parentheses = True
+
+# If true, the current module name will be prepended to all description
+# unit titles (such as .. function::).
+#add_module_names = True
+
+# If true, sectionauthor and moduleauthor directives will be shown in the
+# output. They are ignored by default.
+#show_authors = False
+
+# The name of the Pygments (syntax highlighting) style to use.
+pygments_style = 'sphinx'
+
+# A list of ignored prefixes for module index sorting.
+#modindex_common_prefix = []
+
+
+# -- Options for HTML output ---------------------------------------------------
+
+# The theme to use for HTML and HTML Help pages.  See the documentation for
+# a list of builtin themes.
+html_theme = 'default'
+
+# Theme options are theme-specific and customize the look and feel of a theme
+# further.  For a list of options available for each theme, see the
+# documentation.
+#html_theme_options = {}
+
+# Add any paths that contain custom themes here, relative to this directory.
+#html_theme_path = []
+
+# The name for this set of Sphinx documents.  If None, it defaults to
+# "<project> v<release> documentation".
+#html_title = None
+
+# A shorter title for the navigation bar.  Default is the same as html_title.
+#html_short_title = None
+
+# The name of an image file (relative to this directory) to place at the top
+# of the sidebar.
+#html_logo = None
+
+# The name of an image file (within the static path) to use as favicon of the
+# docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32
+# pixels large.
+#html_favicon = None
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+html_static_path = ['_static']
+
+# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
+# using the given strftime format.
+#html_last_updated_fmt = '%b %d, %Y'
+
+# If true, SmartyPants will be used to convert quotes and dashes to
+# typographically correct entities.
+#html_use_smartypants = True
+
+# Custom sidebar templates, maps document names to template names.
+#html_sidebars = {}
+
+# Additional templates that should be rendered to pages, maps page names to
+# template names.
+#html_additional_pages = {}
+
+# If false, no module index is generated.
+#html_domain_indices = True
+
+# If false, no index is generated.
+#html_use_index = True
+
+# If true, the index is split into individual pages for each letter.
+#html_split_index = False
+
+# If true, links to the reST sources are added to the pages.
+#html_show_sourcelink = True
+
+# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
+#html_show_sphinx = True
+
+# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
+#html_show_copyright = True
+
+# If true, an OpenSearch description file will be output, and all pages will
+# contain a <link> tag referring to it.  The value of this option must be the
+# base URL from which the finished HTML is served.
+#html_use_opensearch = ''
+
+# This is the file name suffix for HTML files (e.g. ".xhtml").
+#html_file_suffix = None
+
+# Output file base name for HTML help builder.
+htmlhelp_basename = 'Beigedoc'
+
+
+# -- Options for LaTeX output --------------------------------------------------
+
+latex_elements = {
+# The paper size ('letterpaper' or 'a4paper').
+#'papersize': 'letterpaper',
+
+# The font size ('10pt', '11pt' or '12pt').
+#'pointsize': '10pt',
+
+# Additional stuff for the LaTeX preamble.
+#'preamble': '',
+}
+
+# Grouping the document tree into LaTeX files. List of tuples
+# (source start file, target name, title, author, documentclass [howto/manual]).
+latex_documents = [
+  ('index', 'Beige.tex', u'Beige Documentation',
+   u'Leonard Ritter', 'manual'),
+]
+
+# The name of an image file (relative to this directory) to place at the top of
+# the title page.
+#latex_logo = None
+
+# For "manual" documents, if this is true, then toplevel headings are parts,
+# not chapters.
+#latex_use_parts = False
+
+# If true, show page references after internal links.
+#latex_show_pagerefs = False
+
+# If true, show URL addresses after external links.
+#latex_show_urls = False
+
+# Documents to append as an appendix to all manuals.
+#latex_appendices = []
+
+# If false, no module index is generated.
+#latex_domain_indices = True
+
+
+# -- Options for manual page output --------------------------------------------
+
+# One entry per manual page. List of tuples
+# (source start file, name, description, authors, manual section).
+man_pages = [
+    ('index', 'liminal', u'Beige Documentation',
+     [u'Leonard Ritter'], 1)
+]
+
+# If true, show URL addresses after external links.
+#man_show_urls = False
+
+
+# -- Options for Texinfo output ------------------------------------------------
+
+# Grouping the document tree into Texinfo files. List of tuples
+# (source start file, target name, title, author,
+#  dir menu entry, description, category)
+texinfo_documents = [
+  ('index', 'Beige', u'Beige Documentation',
+   u'Leonard Ritter', 'Beige', 'One line description of project.',
+   'Miscellaneous'),
+]
+
+# Documents to append as an appendix to all manuals.
+#texinfo_appendices = []
+
+# If false, no module index is generated.
+#texinfo_domain_indices = True
+
+# How to display URL addresses: 'footnote', 'no', or 'inline'.
+#texinfo_show_urls = 'footnote'

          
A => liminal_legacy/doc/source/index.txt +25 -0
@@ 0,0 1,25 @@ 
+.. Beige documentation master file, created by
+   sphinx-quickstart on Thu Jan  3 13:01:37 2013.
+   You can adapt this file completely to your liking, but it should at least
+   contain the root `toctree` directive.
+
+Welcome to Beige's documentation!
+=================================
+
+Contents:
+
+.. toctree::
+   :maxdepth: 2
+
+.. toctree::
+   :maxdepth: 3
+   
+   reference
+
+Indices and tables
+==================
+
+* :ref:`genindex`
+* :ref:`modindex`
+* :ref:`search`
+

          
A => liminal_legacy/doc/source/reference.txt +171 -0
@@ 0,0 1,171 @@ 
+Beige API Reference
+===================
+
+beige.engine
+------------
+
+.. automodule:: beige.engine
+   :show-inheritance:
+   :members:
+   :undoc-members:
+
+beige.engine.audio
+------------------
+
+.. automodule:: beige.engine.audio
+   :show-inheritance:
+   :members:
+   :undoc-members:
+
+beige.engine.camera
+-------------------
+
+.. automodule:: beige.engine.camera
+   :show-inheritance:
+   :members:
+   :undoc-members:
+
+beige.engine.debugdraw
+----------------------
+
+.. automodule:: beige.engine.debugdraw
+   :show-inheritance:
+   :members:
+   :undoc-members:
+
+beige.engine.entity
+-------------------
+
+.. automodule:: beige.engine.entity
+   :show-inheritance:
+   :members:
+   :undoc-members:
+
+beige.engine.font
+-----------------
+
+.. automodule:: beige.engine.font
+   :show-inheritance:
+   :members:
+   :undoc-members:
+
+beige.engine.ghost
+------------------
+
+.. automodule:: beige.engine.ghost
+   :show-inheritance:
+   :members:
+   :undoc-members:
+
+beige.engine.input
+------------------
+
+.. automodule:: beige.engine.input
+   :show-inheritance:
+   :members:
+   :undoc-members:
+
+beige.engine.interface
+----------------------
+
+.. automodule:: beige.engine.interface
+   :show-inheritance:
+   :members:
+   :undoc-members:
+
+beige.engine.logic
+------------------
+
+.. automodule:: beige.engine.logic
+   :show-inheritance:
+   :members:
+   :undoc-members:
+
+beige.engine.material
+---------------------
+
+.. automodule:: beige.engine.material
+   :show-inheritance:
+   :members:
+   :undoc-members:
+
+beige.engine.mesh
+-----------------
+
+.. automodule:: beige.engine.mesh
+   :show-inheritance:
+   :members:
+   :undoc-members:
+
+beige.engine.node
+-----------------
+
+.. automodule:: beige.engine.node
+   :show-inheritance:
+   :members:
+   :undoc-members:
+
+beige.engine.ppfx
+-----------------
+
+.. automodule:: beige.engine.ppfx
+   :show-inheritance:
+   :members:
+   :undoc-members:
+
+beige.engine.render
+-------------------
+
+.. automodule:: beige.engine.render
+   :show-inheritance:
+   :members:
+   :undoc-members:
+
+beige.engine.scene
+------------------
+
+.. automodule:: beige.engine.scene
+   :show-inheritance:
+   :members:
+   :undoc-members:
+
+beige.engine.shader
+-------------------
+
+.. automodule:: beige.engine.shader
+   :show-inheritance:
+   :members:
+   :undoc-members:
+
+beige.engine.texture
+--------------------
+
+
+.. automodule:: beige.engine.texture
+   :show-inheritance:
+   :members:
+   :undoc-members:
+   
+beige.builder
+-------------
+
+.. automodule:: beige.builder
+   :show-inheritance:
+   :members:
+   :undoc-members:
+
+beige.utils
+-----------
+
+.. automodule:: beige.utils
+   :show-inheritance:
+   :members:
+   :undoc-members:
+
+beige.runtime
+-------------
+
+.. automodule:: beige.runtime
+   :show-inheritance:
+   :members:
+   :undoc-members:

          
A => liminal_legacy/liminal/__init__.py +0 -0

        
A => liminal_legacy/liminal/assets/fonts/DejaVuSans.ttf +0 -0

        
A => liminal_legacy/liminal/assets/fonts/SourceSansPro-Black.ttf +0 -0

        
A => liminal_legacy/liminal/assets/fonts/SourceSansPro-BlackItalic.ttf +0 -0

        
A => liminal_legacy/liminal/assets/fonts/SourceSansPro-Bold.ttf +0 -0

        
A => liminal_legacy/liminal/assets/fonts/SourceSansPro-BoldItalic.ttf +0 -0

        
A => liminal_legacy/liminal/assets/fonts/SourceSansPro-ExtraLight.ttf +0 -0

        
A => liminal_legacy/liminal/assets/fonts/SourceSansPro-ExtraLightItalic.ttf +0 -0

        
A => liminal_legacy/liminal/assets/fonts/SourceSansPro-Italic.ttf +0 -0

        
A => liminal_legacy/liminal/assets/fonts/SourceSansPro-Light.ttf +0 -0

        
A => liminal_legacy/liminal/assets/fonts/SourceSansPro-LightItalic.ttf +0 -0

        
A => liminal_legacy/liminal/assets/fonts/SourceSansPro-Regular.ttf +0 -0

        
A => liminal_legacy/liminal/assets/fonts/SourceSansPro-Semibold.ttf +0 -0

        
A => liminal_legacy/liminal/assets/fonts/SourceSansPro-SemiboldItalic.ttf +0 -0

        
A => liminal_legacy/liminal/assets/fonts/UbuntuMono-R.ttf +0 -0

        
A => liminal_legacy/liminal/assets/fonts/raleway_thin.ttf +0 -0

        
A => liminal_legacy/liminal/assets/liminal/a4epro2.beigefont +0 -0

        
A => liminal_legacy/liminal/assets/liminal/button_normal.png +0 -0

        
A => liminal_legacy/liminal/assets/liminal/button_pressed.png +0 -0

        
A => liminal_legacy/liminal/assets/liminal/cursor.png +0 -0

        
A => liminal_legacy/liminal/assets/liminal/dosis.beigefont +0 -0

        
A => liminal_legacy/liminal/assets/liminal/nevis.beigefont +0 -0

        
A => liminal_legacy/liminal/assets/liminal/window.png +0 -0

        
A => liminal_legacy/liminal/assets/liminal/window_bg.png +0 -0

        
A => liminal_legacy/liminal/assets/liminal/window_closing.png +0 -0

        
A => liminal_legacy/liminal/assets/shaders/flat/simple.glsl +130 -0
@@ 0,0 1,130 @@ 
+
+#include "std/std.glsl"
+#include "lib/math.glsl"
+
+#ifndef USE_COLOR_MAP
+#define USE_COLOR_MAP 0
+#endif
+
+#ifndef USE_VERTEX_COLOR
+#define USE_VERTEX_COLOR 0
+#endif
+
+#ifndef USE_DISTANCE_FADE
+#define USE_DISTANCE_FADE 0
+#endif
+
+#ifndef USE_NORMALS
+#define USE_NORMALS 0
+#endif
+#ifndef USE_RIMLIGHT
+#define USE_RIMLIGHT 0
+#endif
+
+#ifndef USE_FLAT_NORMALS
+#define USE_FLAT_NORMALS 0
+#endif
+
+#ifndef USE_VERTEX_ID
+#define USE_VERTEX_ID 1
+#endif
+
+#if USE_COLOR_MAP
+#include "lib/mapping.glsl"
+#endif
+
+#if USE_VERTEX_COLOR
+varying vec4 color;
+#endif
+#if USE_DISTANCE_FADE
+varying vec4 position;
+#endif
+#if USE_NORMALS
+#if USE_FLAT_NORMALS
+flat varying vec3 normal;
+#else
+varying vec3 normal;
+#endif
+#if USE_RIMLIGHT
+varying vec3 view_position;
+#if USE_FLAT_NORMALS
+flat varying vec3 view_normal;
+#else
+varying vec3 view_normal;
+#endif
+#endif
+#endif
+
+#if VERTEX_SHADER
+
+void main(void)
+{
+    vec4 wp = (mtx_model * in_Position);
+    vec4 vp = (mtx_view * wp);
+    gl_Position = mtx_proj * vp;
+    
+#if USE_NORMALS
+    normal = mat3(mtx_model) * in_Normal; // normalize(in_Normal);
+#if USE_RIMLIGHT
+    view_position = vp.xyz;
+    view_normal = mat3(mtx_view) * normal;
+#endif 
+#endif
+
+#if USE_COLOR_MAP
+    write_texcoord();
+#endif
+    
+#if USE_VERTEX_COLOR
+    color = in_Color;
+#endif
+#if USE_DISTANCE_FADE
+    position = gl_Position;
+#endif
+}
+
+#elif FRAGMENT_SHADER
+
+#if USE_COLOR_MAP
+uniform SAMPLER2DTYPE color_map;
+#endif
+
+void main(void) {
+    vec4 final_color = vec4(1.0);
+
+#if USE_COLOR_MAP
+    final_color *= map_texture(color_map);
+#endif
+
+#if USE_QUILTING
+    vec3 spln = get_quilt_normal(normal);
+    vec3 view_spln = normalize(mat3(mtx_view) * spln);
+
+#define FRAG_NORMAL spln
+#define FRAG_VIEW_NORMAL view_spln
+#else // !USE_QUILTING
+#define FRAG_NORMAL normalize(normal)
+#define FRAG_VIEW_NORMAL normalize(view_normal)
+#endif // USE_QUILTING
+
+#if USE_VERTEX_COLOR
+    final_color *= color;
+#endif
+
+#if USE_DISTANCE_FADE
+    final_color *= (1.0 - pow(position.z / position.w, 4.0));
+#endif
+
+#if USE_NORMALS
+    final_color *= mix(0.2, 1.0, max(0.0, 
+    dot(FRAG_NORMAL, vec3(0.26726,-0.53452,0.80178))));
+#if USE_RIMLIGHT    
+    final_color.rgb += vec3(max(0.0,0.5-abs(dot(
+        FRAG_VIEW_NORMAL, normalize(view_position)))));
+#endif
+#endif
+
+    out_Color = final_color;   
+}
+
+#endif // FRAGMENT_SHADER
  No newline at end of file

          
A => liminal_legacy/liminal/assets/shaders/font/font.glsl +26 -0
@@ 0,0 1,26 @@ 
+
+varying float sharpness; 
+varying vec2 texcoord;
+
+#if VERTEX_SHADER
+
+in float in_sharpness;
+
+void write_font_attribs() {
+   // coordinate of the 1st texture channel
+   texcoord = in_TexCoord0;
+   sharpness = in_sharpness;   
+}
+
+#elif FRAGMENT_SHADER
+
+uniform sampler2D font_map;
+
+float read_font_alpha() {
+    vec2 uv = texcoord;
+    float C = max(1.0, sharpness / (max(abs(dFdx(uv.s)), abs(dFdy(uv.t))) * 32.0));
+    float d = texture(font_map, uv).r;
+    return clamp((d-0.5)*C+0.5, 0.0, 1.0);
+}
+
+#endif
  No newline at end of file

          
A => liminal_legacy/liminal/assets/shaders/font/font_gshader.fs +22 -0
@@ 0,0 1,22 @@ 
+
+#include "gshader_frag.glsl"
+#include "font_frag.glsl"
+
+void main(void) {
+    vec2 uv = gl_TexCoord[0];
+
+    GBufferAttributes attribs = gba_default();
+    float d = read_font_alpha(uv);
+    vec3 color = gl_Color.rgb;
+    if (solid) {
+        if (d <= 0.5)
+            discard;
+    } else {
+        color *= d;
+    }
+    attribs.albedo *= color;
+    attribs.light *= color;
+    
+    
+    write_gbuffer(attribs);    
+}

          
A => liminal_legacy/liminal/assets/shaders/font/font_gshader.vs +8 -0
@@ 0,0 1,8 @@ 
+#include "gshader_vert.glsl"
+#include "font_vert.glsl"
+
+void main(void)
+{
+    write_vertex_vars();
+    write_font_attribs();
+}

          
A => liminal_legacy/liminal/assets/shaders/font/simple.glsl +24 -0
@@ 0,0 1,24 @@ 
+
+#include "std/std.glsl"
+#include "font.glsl"
+
+varying vec4 color;
+
+#if VERTEX_SHADER
+
+void main(void)
+{
+   // original vertex position, no changes
+   gl_Position = mtx_proj * (mtx_view * (mtx_model * in_Position));
+   // vertex color
+   color = in_Color;
+   write_font_attribs();
+}
+#elif FRAGMENT_SHADER
+
+void main(void)
+{
+    float d = read_font_alpha();
+    out_Color = color * vec4(d);
+}
+#endif
  No newline at end of file

          
A => liminal_legacy/liminal/assets/shaders/g/gbuffer_attr.glsl +18 -0
@@ 0,0 1,18 @@ 
+
+#ifndef GBA_USE_DEPTH
+#define GBA_USE_DEPTH 0
+#endif
+
+struct GBufferAttributes {
+#if GBA_USE_DEPTH
+    float depth;
+#endif
+    vec3 albedo;
+    float ao;
+    vec2 velocity;
+    vec3 normal;
+    float roughness;
+    float metallic;
+    float sky;
+    vec4 light;
+};

          
A => liminal_legacy/liminal/assets/shaders/g/gshader.glsl +224 -0
@@ 0,0 1,224 @@ 
+
+#include "std/std.glsl"
+
+#ifndef SHADOW_MATERIAL
+#define SHADOW_MATERIAL 0
+#endif
+
+#ifndef DISCARD_ALBEDO_ALPHA
+#define DISCARD_ALBEDO_ALPHA 0
+#endif
+
+#ifndef DISCARD_GLOW_ALPHA
+#define DISCARD_GLOW_ALPHA 0
+#endif
+
+#ifndef USE_GLOW_MAP
+#define USE_GLOW_MAP 0
+#endif
+
+#ifndef USE_GLOSS_MAP
+#define USE_GLOSS_MAP 0
+#endif
+#ifndef USE_HEIGHT_MAP
+#define USE_HEIGHT_MAP 0
+#endif
+#ifndef USE_DERIVATIVE_MAP
+#define USE_DERIVATIVE_MAP 0
+#endif
+#ifndef USE_ALBEDO_MAP
+#define USE_ALBEDO_MAP 0
+#endif 
+
+#ifndef USE_TEXTURE_ARRAY
+#define USE_TEXTURE_ARRAY 0
+#endif
+
+
+
+#if SHADOW_MATERIAL
+#if ((USE_ALBEDO_MAP && DISCARD_ALBEDO_ALPHA) || (USE_GLOW_MAP && DISCARD_GLOW_ALPHA))
+#define USE_SHADOW_UV 1 
+#else
+#define USE_SHADOW_UV 0
+#endif
+#endif // SHADOW_MATERIAL
+
+#include "lib/mapping.glsl"
+
+#if VERTEX_SHADER
+
+#if SHADOW_MATERIAL
+#if USE_SHADOW_UV
+out VECUVTYPE v_texcoord;
+#endif
+#else
+#include "gshader_vars.glsl"
+#include "gshader_vert.glsl"
+#endif
+
+void main(void)
+{
+#if SHADOW_MATERIAL
+#if USE_SHADOW_UV
+    v_texcoord = IN_TEXCOORD;
+#endif
+    gl_Position = mtx_proj * (mtx_view * (mtx_model * vec4(in_Position.xyz,1.0)));
+#else
+    write_vertex_vars();
+#endif
+}
+
+#elif GEOMETRY_SHADER
+
+layout(triangles) in;
+layout(triangle_strip, max_vertices = LAYER_GS_VERTEX_COUNT) out;
+
+uniform vec4 dm_offsets[LAYER_COUNT];
+
+#if USE_SHADOW_UV
+in VECUVTYPE v_texcoord[3];
+out VECUVTYPE f_texcoord;
+#endif
+
+void main() {
+  for (int k = 0; k < LAYER_COUNT; ++k) {
+        for(int i = 0; i < 3; ++i) {
+            gl_Layer = k;
+            vec4 p = gl_in[i].gl_Position;
+            vec4 o = dm_offsets[k];
+#if USE_SHADOW_UV
+            f_texcoord = v_texcoord[i]; 
+#endif
+            gl_Position = vec4(p.xy * o.xy + o.zw, p.zw);
+            EmitVertex();
+        }
+        EndPrimitive();
+  }
+}
+
+#elif FRAGMENT_SHADER
+
+#if SHADOW_MATERIAL
+#include "sm/shadow_frag.glsl"
+
+#if USE_SHADOW_UV
+in VECUVTYPE f_texcoord;
+#endif
+
+#else
+
+#include "gshader_vars.glsl"
+#include "gshader_frag.glsl"
+#include "lib/perturb.glsl"
+#include "lib/sh.glsl"
+
+#endif
+
+#ifndef HEIGHT_MAP_FACTOR
+#define HEIGHT_MAP_FACTOR 1.0
+#endif
+
+//#define USE_POM
+#ifdef USE_POM
+#include "pom/pom_frag.glsl"
+#endif
+
+#if USE_ALBEDO_MAP
+uniform SAMPLER2DTYPE albedo_map;
+#endif
+#if USE_HEIGHT_MAP
+uniform SAMPLER2DTYPE height_map;
+#endif
+#if USE_GLOSS_MAP
+uniform SAMPLER2DTYPE gloss_map;
+#endif
+#if USE_GLOW_MAP
+uniform SAMPLER2DTYPE glow_map;
+#endif
+
+#ifndef DISCARD_ALPHA_MAX
+#define DISCARD_ALPHA_MAX 0.729
+#endif
+
+void main(void) {
+#if SHADOW_MATERIAL
+#if USE_SHADOW_UV
+    VECUVTYPE uv = f_texcoord;
+#endif
+#else
+#if !USE_QUILTING
+    VECUVTYPE uv = texcoord;
+#endif
+#ifdef USE_POM
+    uv = calc_pom_parallax_uv(height_map, uv);
+#endif
+#endif
+
+#if USE_ALBEDO_MAP && (!SHADOW_MATERIAL || DISCARD_ALBEDO_ALPHA)
+    vec4 albedo = map_texture(albedo_map);
+#endif
+#if USE_GLOW_MAP && (!SHADOW_MATERIAL || DISCARD_GLOW_ALPHA)
+    vec4 glow = map_texture(glow_map);
+#endif
+
+#if !SHADOW_MATERIAL
+#if USE_GLOSS_MAP
+    float gloss = map_texture(gloss_map).r;
+#endif    
+
+    GBufferAttributes attribs = gba_default();
+
+#if USE_HEIGHT_MAP
+    vec3 h_normal;
+#if USE_DERIVATIVE_MAP
+    h_normal = perturb_normal_derivative_mapped(mv_vertex, attribs.normal, height_map, uv);
+#else // !USE_DERIVATIVE_MAP
+    h_normal = perturb_normal_mapped(mv_vertex, attribs.normal, height_map, uv);
+#endif // !USE_DERIVATIVE_MAP
+#endif // USE_HEIGHT_MAP
+#endif
+
+#if USE_LRMANNEQ
+    vec2 mat0; vec4 mat1; vec3 nn; vec4 base_color;
+    map_texture_dm(albedo_map, mat0, mat1, nn, base_color);
+    attribs.normal = mat3(mtx_model) * nn;
+    
+    albedo.rgb = base_color.xyz * mat1.x;
+    //attribs.ao *= base_color.w;
+    attribs.light.rgb *= base_color.xyz * base_color.w * mat1.y;
+    attribs.roughness *= mat1.z;
+    attribs.metallic *= mat1.w;
+#endif
+
+#if USE_ALBEDO_MAP && DISCARD_ALBEDO_ALPHA
+    if (albedo.a < DISCARD_ALPHA_MAX)
+        discard;
+#endif
+#if USE_GLOW_MAP && DISCARD_GLOW_ALPHA && (SHADOW_MATERIAL || !USE_ADDITIVE) 
+    if (glow.a < DISCARD_ALPHA_MAX)
+        discard;
+#endif
+    
+#if SHADOW_MATERIAL
+    write_shadow_frag();
+#else // !SHADOW_MATERIAL
+#if USE_ALBEDO_MAP    
+    attribs.albedo *= albedo.rgb;
+#endif    
+#if USE_GLOW_MAP
+    attribs.light.rgb *= glow.rgb;
+#endif
+#if USE_GLOSS_MAP    
+    attribs.roughness *= gloss;
+#endif
+#if USE_HEIGHT_MAP
+    attribs.normal = normalize(mix(attribs.normal, h_normal, HEIGHT_MAP_FACTOR));
+#endif
+
+    write_gbuffer(attribs);
+#endif // !SHADOW_MATERIAL
+   
+}
+
+#endif // FRAGMENT_SHADER
  No newline at end of file

          
A => liminal_legacy/liminal/assets/shaders/g/gshader_frag.glsl +99 -0
@@ 0,0 1,99 @@ 
+#ifndef USE_ADDITIVE
+#define USE_ADDITIVE 0
+#endif 
+
+#ifndef USE_VERTEX_COLOR
+#define USE_VERTEX_COLOR 0
+#endif
+
+#ifndef USE_BACKFACE_NORMALS
+#define USE_BACKFACE_NORMALS 0
+#endif
+
+#ifndef USE_GAMMA2
+#define USE_GAMMA2 0
+#endif
+
+// updates here must also be updated in FragDataLocations
+#define out_albedo out_Color
+out vec2 out_matfx;
+out vec2 out_normal;
+out vec4 out_light;
+out ivec2 out_velocity;
+
+#include "lib/normal_codec.glsl"
+#include "gbuffer_attr.glsl"
+
+#if USE_GAMMA2
+#define GAMMAFIX(X) ((X)*(X))
+#else
+#define GAMMAFIX(X) X
+#endif
+
+#if USE_VERTEX_COLOR
+#define VERTEX_COLOR(X) (vertex_color * X)
+#else
+#define VERTEX_COLOR(X) X
+#endif
+
+vec2 gba_default_velocity() {
+    vec2 p0 = (mvp_vertex.xy / mvp_vertex.w)*0.5 + 0.5;
+    vec2 p1 = (last_mvp_vertex.xy / last_mvp_vertex.w)*0.5 + 0.5;
+    return p0 - p1;
+}
+
+GBufferAttributes gba_default() {
+#if USE_BACKFACE_NORMALS
+    vec3 fixed_normal = normal * (float(gl_FrontFacing)*2.0 - 1.0);
+#define GBA_NORMAL fixed_normal
+#else
+#define GBA_NORMAL normal
+#endif
+    return GBufferAttributes(
+        VERTEX_COLOR(albedo),
+        1.0,
+        gba_default_velocity(),
+        GBA_NORMAL,
+        roughness_factor,
+        metallic_factor,
+        0.0,
+        vec4(VERTEX_COLOR(emissive_color) * emissive, 0.0));
+#undef GBA_NORMAL        
+}
+
+GBufferAttributes gba_empty() {
+    return GBufferAttributes(
+        vec3(0.0),
+        1.0,
+        vec2(0.0),
+        vec3(0.0),
+        0.0,
+        0.0,
+        0.0,
+        vec4(0.0));
+}
+
+void write_gbuffer(in GBufferAttributes attribs) {
+#if USE_ADDITIVE
+    out_albedo.rgba = vec4(0.0, 0.0, 0.0, 0.0);
+    out_matfx.rg = vec2(0.0);
+    out_normal.rg = vec2(0.0);
+    out_velocity.rg = ivec2(0);
+#else
+    out_albedo.rgba = vec4(attribs.albedo, attribs.ao);
+    out_matfx.rg = vec2(
+        attribs.roughness + roughness_bias, attribs.metallic);
+    out_normal.rg = vec2(
+        encode_normal(normalize(attribs.normal)));
+    out_velocity.rg = ivec2(attribs.velocity*127.0);
+#endif
+    out_light = GAMMAFIX(attribs.light);
+}
+
+void write_gbuffer_depth_vel_fx(in GBufferAttributes attribs) {
+    out_albedo.rgba = vec4(0.0, 0.0, 0.0, 0.0);
+    out_matfx.rg = vec2(attribs.roughness, attribs.metallic);
+    out_normal.rg = vec2(0.0);
+    out_velocity.rg = ivec2(attribs.velocity*127.0);
+    out_light = vec4(0.0);
+}

          
A => liminal_legacy/liminal/assets/shaders/g/gshader_sdf.glsl +216 -0
@@ 0,0 1,216 @@ 
+/*
+These three functions must be declared outside, only for the fragment
+shader: dC and dF
+
+see below for signatures.
+*/
+
+#ifndef DEBUG_STEPS
+#define DEBUG_STEPS 0
+#endif
+
+#ifndef SHADOW_MATERIAL
+#define SHADOW_MATERIAL 0
+#endif
+
+struct VXData {
+    mat3 mtx_toray;
+    mat4 mtx_modelview4;
+    float view_z; // offset
+#if SHADOW_MATERIAL
+    vec3 vertex;
+#else
+    mat3 mtx_modelview3;
+    mat4 mtx_last_mvp;
+#endif
+};
+
+#if VERTEX_SHADER
+
+#if !SHADOW_MATERIAL
+#include "g/gshader_vert.glsl"
+#endif
+
+out VXData data;
+
+void main(void)
+{
+    data.mtx_modelview4 = mtx_view * mtx_model;
+    vec4 p = (data.mtx_modelview4 * in_Position);
+#if SHADOW_MATERIAL
+    gl_Position = mtx_proj * p;
+#else
+    write_vertex_vars();
+#endif
+    mat3 mv3 = mat3(data.mtx_modelview4);
+#if SHADOW_MATERIAL
+    data.vertex = in_Position.xyz;
+    data.view_z = 1.0;
+#else
+    data.mtx_modelview3 = mv3;
+    data.mtx_last_mvp = mtx_last_mvp();
+    data.view_z = -p.z;
+#endif
+    data.mtx_toray = transpose(mv3);
+}
+
+#elif GEOMETRY_SHADER // shadow material only
+
+layout(triangles) in;
+layout(triangle_strip, max_vertices = LAYER_GS_VERTEX_COUNT) out;
+
+in VXData data[3];
+out VXData data_out;
+
+uniform vec4 dm_offsets[LAYER_COUNT];
+
+void main() {
+  for (int k = 0; k < LAYER_COUNT; ++k) {
+        for(int i = 0; i < 3; ++i) {
+            gl_Layer = k;
+            vec4 p = gl_in[i].gl_Position;
+            vec4 o = dm_offsets[k];
+            data_out.mtx_toray = data[i].mtx_toray;
+            data_out.mtx_modelview4 = data[i].mtx_modelview4;
+            data_out.vertex = data[i].vertex;
+            data_out.view_z = data[i].view_z;
+            gl_Position = vec4(p.xy * o.xy + o.zw, p.zw);
+            EmitVertex();
+        }
+        EndPrimitive();
+  }
+}
+
+#elif FRAGMENT_SHADER
+
+#include "g/gshader_frag.glsl"
+
+#if SHADOW_MATERIAL
+#define data data_out
+#endif
+in VXData data;
+
+void dC(vec3 p, float z, inout GBufferAttributes attribs);
+float dF(vec3 p, float z);
+
+// max number of steps to trace for the raw function
+#ifndef RAY_STEP
+#define RAY_STEP 64
+#endif
+
+uniform float texel_size;
+
+#if SHADOW_MATERIAL
+#define TEXEL_SIZE (0.7071 / 1024.0)
+#else
+#define TEXEL_SIZE texel_size
+#endif
+
+// what delta width to pick for
+// normal derivation (width = 2*NORMAL_DX)
+#ifndef NORMAL_DX
+#define NORMAL_DX 0.01
+#endif
+
+// abort after which depth
+#ifndef MAX_DEPTH
+// diagonal of unit cube
+#define MAX_DEPTH 3.4641
+#endif
+
+//const vec2 dx = vec2(NORMAL_DX,0.0);
+
+void write_point(vec3 p, int steps, float t, float z) {
+#if SHADOW_MATERIAL
+    vec4 pw = data.mtx_modelview4 * vec4(p,1.0);
+    
+    float depth = (mtx_proj * pw).z;
+    float d1=gl_DepthRange.far; float d0=gl_DepthRange.near;
+    gl_FragDepth = (((d1-d0) * depth) + d0 + d1) / 2.0;
+    out_Color = vec4(1.0);
+#else
+    //vec2 dx = vec2(max(NORMAL_DX,t), 0.0);
+    vec2 dx = vec2(t*4.0, 0.0);
+
+    vec3 n = vec3(
+        dF(p + dx.xyy,z) - dF(p - dx.xyy,z),
+        dF(p + dx.yxy,z) - dF(p - dx.yxy,z),
+        dF(p + dx.yyx,z) - dF(p - dx.yyx,z)
+    );
+    
+    GBufferAttributes attribs = gba_default();
+    
+    vec4 pw = data.mtx_modelview4 * vec4(p,1.0);
+    float lin_depth = pw.z;
+    
+    vec4 v0 = mtx_proj * pw;
+    vec4 v1 = data.mtx_last_mvp * vec4(p,1.0);
+    
+    vec2 p0 = (v0.xy / v0.w)*0.5 + 0.5;
+    vec2 p1 = (v1.xy / v1.w)*0.5 + 0.5;
+    
+    attribs.depth = -lin_depth / far;
+    attribs.normal = data.mtx_modelview3 * n;
+    attribs.velocity = p0 - p1;
+#if DEBUG_STEPS
+    attribs.roughness = 1.0;
+    attribs.metallic = 0.0;
+    attribs.albedo = vec3(0.0);
+    float ds = mod(float(steps), 2.0);
+    float dm2 = step(float(RAY_STEP) / 2.0, float(steps));
+    float dm = step(float(RAY_STEP-2), float(steps));
+    
+    attribs.light = 6.0 * vec3(2.0*dm2,ds,4.0*dm);
+#else
+    dC(p, z, attribs);
+#endif    
+    
+    write_gbuffer(attribs);
+#endif
+}
+
+vec3 vdir;
+float view_z_scale = 0.0;
+
+void trace(vec3 p) {
+    float d = 0.0;
+    float r = 0.0;
+    float t = 0.0;
+    float z = data.view_z;
+    
+    for (int i = 0; i < RAY_STEP; ++i) {
+        d = dF(p, z);
+        t = TEXEL_SIZE * z;
+        if (d <= t) {
+            write_point(p, i, t, z);
+            return;
+        }
+        d = max(d, t);
+        r += d;
+        if (r >= MAX_DEPTH) {
+            discard;
+        }
+        z += d * view_z_scale;
+        p += vdir * d;
+    }
+    discard;
+}
+
+void main(void) {
+#if SHADOW_MATERIAL
+    vec3 model_ray = -data.mtx_toray[2];
+#else
+    vec3 model_ray = data.mtx_toray * mv_vertex.xyz;
+#endif
+
+    vdir = normalize(model_ray);
+#if SHADOW_MATERIAL
+    vec3 p = data.vertex;
+#else
+    view_z_scale = -(data.mtx_modelview3 * vdir).z;
+    vec3 p = vertex;
+#endif
+    trace(p);
+}
+
+#endif // FRAGMENT_SHADER
  No newline at end of file

          
A => liminal_legacy/liminal/assets/shaders/g/gshader_vars.glsl +18 -0
@@ 0,0 1,18 @@ 
+varying vec3 vertex; // original vertex as in bufer
+varying vec3 m_vertex; // model transformed vertex (world space)
+varying vec3 mv_vertex; // model view transformed vertex (view space)
+varying vec3 normal; // normal (world space)
+varying vec4 mvp_vertex; // model view projection transformed vertex (screen space)
+varying vec4 last_mvp_vertex; // current vertex as transformed by previous frame (screen space)
+varying vec3 vertex_color; // vertex color
+varying vec4 light_color; // light color
+
+layout(std140) uniform GMaterialAttribs {
+    float roughness_factor;// = 1.0;
+    float roughness_bias;// = 0.0;   
+    float metallic_factor;// = 1.0;
+    vec3 albedo;// = vec3(1.0);
+    vec3 emissive_color;// = vec3(1.0);    
+    float emissive;// = 0.0;
+}; 
+

          
A => liminal_legacy/liminal/assets/shaders/g/gshader_vert.glsl +65 -0
@@ 0,0 1,65 @@ 
+#ifndef USE_BILLBOARD
+#define USE_BILLBOARD 0
+#endif
+
+#ifndef USE_PARTICLES
+#define USE_PARTICLES 0 
+#endif
+
+vec3 orthogonal(vec3 v)
+{
+    return abs(v.x) > abs(v.z) ? vec3(-v.y, v.x, 0.0)
+                               : vec3(0.0, -v.z, v.y);
+}
+
+void write_vertex_vars() {
+    vec4 world_vertex;
+    vec4 view_vertex;
+    
+    vec4 inp = vec4(in_Position.xyz,1.0);
+#if USE_PARTICLES
+    vec3 up = normalize(in_Velocity);
+    vec3 right = orthogonal(up);
+    mat3 mtx_rotate = mat3(right, cross(up,right), up);
+    inp.xyz = (mtx_rotate * inp.xyz) + in_Origin;
+    vec4 last_inp = inp;
+    last_inp.xyz += in_Velocity * (1.0/60.0);
+#else
+#define last_inp inp
+#endif    
+
+#if USE_BILLBOARD
+    const vec4 center = vec4(0.0,0.0,0.0,1.0);
+    vec4 offset = vec4(inp.xyz, 1.0);
+
+    world_vertex = mtx_model * center;
+    view_vertex = mtx_view * world_vertex + offset;
+    mvp_vertex = mtx_proj * view_vertex;
+    last_mvp_vertex = mtx_last_mvp() * center + mtx_proj * offset;
+#else
+    world_vertex = mtx_model * inp;
+    view_vertex = mtx_view * world_vertex;
+    mvp_vertex = mtx_proj * view_vertex;
+    last_mvp_vertex = mtx_last_mvp() * last_inp;
+#endif
+        
+    vertex = inp.xyz;
+    m_vertex = world_vertex.xyz;
+    mv_vertex = view_vertex.xyz;
+
+    gl_Position = mvp_vertex;
+    write_texcoord();
+    vertex_color = in_Color.rgb;
+    light_color = in_Color2;
+    
+#ifdef USE_POM
+    calc_pom_parallax();
+#endif
+
+
+#if USE_PARTICLES
+    normal = mat3(mtx_model) * (mtx_rotate * in_Normal);
+#else
+    normal = mat3(mtx_model) * in_Normal;
+#endif    
+}
  No newline at end of file

          
A => liminal_legacy/liminal/assets/shaders/g/lpv/propagate.glsl +210 -0
@@ 0,0 1,210 @@ 
+#include "std/std.glsl"
+
+// simulate occlusion
+#define USE_LPV_OCCLUSION 1
+
+// simulate light bounces
+#define USE_LPV_BOUNCE 1
+
+// clamp negative influx
+#define USE_LPV_CLAMP_INFLUX 1
+
+noperspective varying vec2 window_xy;
+noperspective varying vec2 griduv;
+noperspective varying vec2 uv;
+uniform int igridsize;
+float gridsize = float(igridsize);
+
+#ifndef FIRST_BOUNCE
+#define FIRST_BOUNCE 0
+#endif
+
+#if VERTEX_SHADER
+
+void main(void) {
+    gl_Position = vec4(in_Position.xy, 0.0, 1.0);
+    window_xy = in_Position.xy;
+    
+    uv = (window_xy * 0.5 + 0.5);
+    vec2 size = vec2(gridsize*gridsize, gridsize*3.0);
+    griduv = uv*size;
+}
+
+#elif FRAGMENT_SHADER
+
+out vec4 out_accum;
+
+uniform sampler2D lpv;
+#if !FIRST_BOUNCE
+uniform sampler2D accum;
+#endif
+uniform sampler2D vocc;
+uniform sampler2D vcolor;
+uniform float occlusion_factor = 1.0;
+uniform float bounce_factor = 1.0;
+
+int channel;
+
+vec4 sh_project(vec3 n) {
+    return vec4(
+        0.282094791773878140, 
+        -0.488602511902919920 * n.y,
+        0.488602511902919920 * n.z,
+        -0.488602511902919920 * n.x);
+}
+
+vec4 lpv_read1(sampler2D s, ivec3 p) {
+    float pmn = float(min(p.x,min(p.y,p.z)));
+    float pmx = float(max(p.x,max(p.y,p.z)));
+    float f = step(0.0, pmn) *
+        step(pmx, gridsize-1.0);        
+    return texelFetch(s, 
+        ivec2(p.x+p.y*igridsize, p.z), 0) * f;
+}
+
+vec4 lpv_read3(sampler2D s, ivec3 p) {
+    float pmn = float(min(p.x,min(p.y,p.z)));
+    float pmx = float(max(p.x,max(p.y,p.z)));
+    float f = step(0.0, pmn) * 
+        step(pmx, gridsize-1.0);
+    return texelFetch(s, ivec2(p.x+p.y*igridsize, 
+        p.z+channel*igridsize), 0) * f;
+}
+
+void main() {
+    ivec2 iuv = ivec2(griduv);
+    ivec3 ipos = ivec3(
+        iuv.x % igridsize,
+        iuv.x / igridsize,
+        iuv.y % igridsize);
+    channel = iuv.y / igridsize;
+
+    vec4 shsumcoeffs = vec4(0.0);
+
+    #if USE_LPV_OCCLUSION || USE_LPV_BOUNCE
+    vec4 gv4[6];
+    vec4 gv[8];
+    #if USE_LPV_BOUNCE
+    vec4 bc4[6];
+    vec4 bc[8];
+    #endif // USE_LPV_BOUNCE
+    #endif // USE_LPV_OCCLUSION || USE_LPV_BOUNCE
+    
+    #if USE_LPV_OCCLUSION || USE_LPV_BOUNCE
+    gv[0] = lpv_read1(vocc, ipos + ivec3(0,0,0));
+    gv[1] = lpv_read1(vocc, ipos + ivec3(0,0,1));
+    gv[2] = lpv_read1(vocc, ipos + ivec3(0,1,0));
+    gv[3] = lpv_read1(vocc, ipos + ivec3(0,1,1));
+    gv[4] = lpv_read1(vocc, ipos + ivec3(1,0,0));
+    gv[5] = lpv_read1(vocc, ipos + ivec3(1,0,1));
+    gv[6] = lpv_read1(vocc, ipos + ivec3(1,1,0));
+    gv[7] = lpv_read1(vocc, ipos + ivec3(1,1,1));
+    
+    #if USE_LPV_BOUNCE
+    bc[0] = lpv_read1(vcolor, ipos + ivec3(0,0,0));
+    bc[1] = lpv_read1(vcolor, ipos + ivec3(0,0,1));
+    bc[2] = lpv_read1(vcolor, ipos + ivec3(0,1,0));
+    bc[3] = lpv_read1(vcolor, ipos + ivec3(0,1,1));
+    bc[4] = lpv_read1(vcolor, ipos + ivec3(1,0,0));
+    bc[5] = lpv_read1(vcolor, ipos + ivec3(1,0,1));
+    bc[6] = lpv_read1(vcolor, ipos + ivec3(1,1,0));
+    bc[7] = lpv_read1(vcolor, ipos + ivec3(1,1,1));
+    #endif    
+    
+    gv4[0] = (gv[0]+gv[1]+gv[2]+gv[3])*0.25;
+    gv4[1] = (gv[4]+gv[5]+gv[6]+gv[7])*0.25;
+    gv4[2] = (gv[0]+gv[4]+gv[1]+gv[5])*0.25;
+    gv4[3] = (gv[2]+gv[6]+gv[3]+gv[7])*0.25;
+    gv4[4] = (gv[0]+gv[2]+gv[4]+gv[6])*0.25;
+    gv4[5] = (gv[1]+gv[3]+gv[5]+gv[7])*0.25;
+    
+    #if USE_LPV_BOUNCE
+    bc4[0] = (bc[0]+bc[1]+bc[2]+bc[3])*0.25;
+    bc4[1] = (bc[4]+bc[5]+bc[6]+bc[7])*0.25;
+    bc4[2] = (bc[0]+bc[4]+bc[1]+bc[5])*0.25;
+    bc4[3] = (bc[2]+bc[6]+bc[3]+bc[7])*0.25;
+    bc4[4] = (bc[0]+bc[2]+bc[4]+bc[6])*0.25;
+    bc4[5] = (bc[1]+bc[3]+bc[5]+bc[7])*0.25;
+    #endif
+    
+    #endif // USE_LPV_OCCLUSION || USE_LPV_BOUNCE 
+    
+    for (int neighbor = 0; neighbor < 6; neighbor++) {
+        ivec3 offset = ivec3(0);
+        int dim = 2-(neighbor>>1);
+        offset[dim] = ((neighbor&1)<<1)-1;
+        
+        vec4 shcoeffs = lpv_read3(lpv, ipos+offset);
+        
+        #if USE_LPV_OCCLUSION
+        vec4 gvcoeffs = gv4[neighbor];
+        #endif
+        
+        vec3 foffset = vec3(offset);
+        
+        for (int face = 0; face < 6; ++face) {
+            if (face == neighbor) continue;
+        
+            int face_dim = 2-(face>>1);
+            
+            vec3 face_offset = vec3(0.0);
+            face_offset[face_dim] = float(((face&1)<<1)-1)*0.5;
+            
+            vec3 dirw = normalize(face_offset - foffset);
+
+            //angle = (4.0*atan(sqrt(11.0)/33.0));
+            //angle = (-M_PI/3.0+2.0*atan(sqrt(11.0)*3.0/11.0));
+            float solid_angle = (dim == face_dim)?0.4006696846462392:0.4234313544367392;
+        
+            #if USE_LPV_BOUNCE
+            vec4 gvrefcoeffs = gv4[face];
+            vec4 gvrefcolor = bc4[face];
+            #endif
+            
+            vec4 outdirsh = sh_project(dirw);
+            vec4 indirsh = outdirsh;
+            vec4 invindirsh = sh_project(-dirw);
+            
+            // how much flux has been received
+            float influx = dot(shcoeffs, indirsh) * solid_angle;
+            
+            #if USE_LPV_CLAMP_INFLUX
+            influx = max(0.0,influx);
+            #endif
+
+            // how much flux will be occluded
+            #if USE_LPV_OCCLUSION
+            float occluded = clamp(occlusion_factor*dot(gvcoeffs, indirsh),0.0,1.0);
+            #else
+            float occluded = 0.0;
+            #endif
+
+            // how much flux will be passed on
+            float outflux = (1.0 - occluded);
+            
+            // how much flux will be reflected
+            #if USE_LPV_BOUNCE
+            float reflected = outflux * clamp(bounce_factor*dot(gvrefcoeffs, invindirsh),0.0,1.0);
+            if (reflected > 0.0) {
+                float wflux = reflected * influx * gvrefcolor[channel];
+                shsumcoeffs += gvrefcoeffs * wflux;
+            }
+            #endif
+
+            shsumcoeffs += outdirsh * (influx * outflux);
+        }
+    }
+    
+    // write back flux
+    
+    out_Color = shsumcoeffs;
+
+#if !FIRST_BOUNCE
+    vec4 shaccum = texelFetch(accum, iuv, 0);
+    out_accum = shaccum + shsumcoeffs;
+#else    
+    out_accum = shsumcoeffs;
+#endif
+}
+
+#endif

          
A => liminal_legacy/liminal/assets/shaders/g/ppfx/dof-mblur.glsl +232 -0
@@ 0,0 1,232 @@ 
+#include "std/std.glsl"
+
+#include "g/ppfx/view_ray.glsl"
+
+#ifndef NUM_SAMPLES
+#if VIDEO_QUALITY >= VQ_HIGHEST
+#define NUM_SAMPLES 64
+#elif VIDEO_QUALITY >= VQ_HIGHER
+#define NUM_SAMPLES 16
+#else
+#define NUM_SAMPLES 8
+#endif
+#endif
+
+#ifndef NOISE_DITHER
+#define NOISE_DITHER 1
+#endif
+
+#ifndef USE_DOF
+#define USE_DOF 1
+#endif
+
+#ifndef DOF_RING
+#define DOF_RING 0
+#endif
+
+#ifndef USE_MBLUR
+#define USE_MBLUR 1
+#endif
+
+#ifndef DEBUG_FOCUS
+#define DEBUG_FOCUS 0
+#endif
+
+#if FRAGMENT_SHADER
+
+#include "lib/math.glsl"
+
+uniform sampler2D fb_color0;
+uniform vec2 fb_size;
+uniform vec2 fb_texel;
+
+#if USE_MBLUR
+uniform isampler2D tex_velocity;
+uniform float blur_scale = 1.0;
+#if 1
+#define OFFSET_CENTER - 0.5
+#else
+#define OFFSET_CENTER
+#endif
+#endif
+
+#if USE_DOF
+uniform sampler2D depth_texture;
+
+uniform vec2 focalDepthRange = vec2(1.0, 100.0); // min/max focal distance in m
+uniform float focalDepth = 1.0;  //focal distance value as depth normal (0.0-1.0)
+uniform float focalLength = 35.0; //22.3; //focal length in mm (human eye)
+// The f-number of the human eye varies from about f/8.3 in a very brightly lit place to about f/2.1 in the dark
+// the focal length of the eye is a bit longer, resulting in minimum f-number of f/3.2.
+uniform float fstop = 3.2; //f-stop value
+
+const float CoC = 0.03;//circle of confusion size in mm (35mm film = 0.03mm)
+
+vec3 debugFocus(vec3 col, float blur, float depth)
+{
+    float edge = 0.002*depth; //distance based edge smoothing
+    float m = clamp(smoothstep(0.0,edge,blur),0.0,1.0);
+    float e = clamp(smoothstep(1.0-edge,1.0,blur),0.0,1.0);
+    
+    col = mix(col,vec3(1.0,0.5,0.0),(1.0-m)*0.6);
+    col = mix(col,vec3(0.0,0.5,1.0),((1.0-e)-(1.0-m))*0.2);
+
+    return col;
+}
+
+float linearize(float z) {
+    float zn = 2.0*z-1.0;
+    float zd = (zn * mtx_proj[3][3] - mtx_proj[3][2]) / (zn * mtx_proj[2][3] - mtx_proj[2][2]);
+    return -view_ray.z * (zd / far);
+}
+
+float read_linear_z(vec2 uv) {
+    return linearize(texture(depth_texture, uv).r); 
+}
+
+#if DOF_RING
+vec2 offset_spiral(float x) {
+    float a = x * 2.0 * M_PI;
+    return vec2(a, 1.0);
+}
+#else
+vec2 offset_spiral(float x) {
+#if NUM_SAMPLES == 16
+    float a = x * (2.0 * M_PI * 0.3819444444 * 16.0 * 23.0);
+#elif NUM_SAMPLES == 8
+    float a = x * (2.0 * M_PI * 0.3819444444 * 8.0 * 23.0);
+#else
+    float a = x * (2.0 * M_PI * 0.3819444444 * 64.0 * 67.0);
+#endif
+    return vec2(a, sqrt(x));
+}
+#endif
+
+#endif
+
+vec3 color(vec2 coords) //processing the sample
+{
+    return texture(fb_color0,coords).rgb;
+}
+
+vec2 read_velocity() {
+    return vec2(texture(tex_velocity, screen_texcoord).rg) / 127.0;
+}
+
+vec3 calc_dof_mblur() 
+{
+#if USE_DOF
+    //scene depth calculation
+    float depth = read_linear_z(screen_texcoord);
+    
+    //focal plane calculation
+    
+    float fDepth = clamp(linearize(focalDepth), focalDepthRange.x, focalDepthRange.y);
+    
+    //dof blur factor calculation
+    
+    float blur = 0.0;
+    {
+        float f = focalLength; //focal length in mm
+        float d = fDepth*1000.0; //focal plane in mm
+        float o = depth*1000.0; //depth in mm
+        
+        float a = (o*f)/(o-f); 
+        float b = (d*f)/(d-f); 
+        float c = (d-f)/(d*fstop*CoC); 
+        
+        blur = (abs(a-b)*c)/1000.0;
+    }
+    
+    // calculation of pattern for ditering
+    
+    // getting blur x and y step factor
+    
+    vec2 stepf = vec2(blur, blur*fb_size.x/fb_size.y);
+    vec2 pcofs;
+    vec2 po;
+#endif    
+
+#if USE_MBLUR
+    vec2 velocity = read_velocity() * blur_scale;
+    vec2 vo;    
+#endif
+    
+    // calculation of final color
+    
+    vec3 col = vec3(0.0); 
+    
+    float d = 1.0 / float(NUM_SAMPLES);
+#if NOISE_DITHER
+    float o = d*random(vec3(screen_texcoord,mod(time,1.0)));
+#else    
+    float o = 0.0;
+#endif
+    float r = 0.0;
+    
+#if USE_MBLUR && USE_DOF
+    float w, b0, b1;    
+    b0 = min(1.0, length(velocity)*fb_size.y*0.25);
+    b1 = 1.0 - b0;
+#define READ_SAMPLE() \
+    pcofs = offset_spiral(o); \
+    po = vec2(cos(pcofs.x),sin(pcofs.x))*pcofs.y; \
+    w = pcofs.y * b1 + b0; \
+    r += w; \
+    vo = velocity * (o OFFSET_CENTER); \
+    o += d; \
+    col += color(screen_texcoord + po*stepf + vo)*w;
+#elif USE_MBLUR
+#define READ_SAMPLE() \
+    r += 1.0; \
+    vo = velocity * (o OFFSET_CENTER); \
+    o += d; \
+    col += color(screen_texcoord + vo);
+#elif USE_DOF
+#define READ_SAMPLE() \
+    pcofs = offset_spiral(o); \
+    po = vec2(cos(pcofs.x),sin(pcofs.x))*pcofs.y; \
+    r += pcofs.y; \
+    o += d; \
+    col += color(screen_texcoord + po*stepf)*pcofs.y;
+#endif
+
+#if NUM_SAMPLES > 16
+    for (int i = 0; i < NUM_SAMPLES; ++i) {
+        READ_SAMPLE();
+    }
+#else        
+    READ_SAMPLE();
+    READ_SAMPLE();
+    READ_SAMPLE();
+    READ_SAMPLE();
+    READ_SAMPLE();
+    READ_SAMPLE();
+    READ_SAMPLE();
+    READ_SAMPLE();
+#if NUM_SAMPLES > 8    
+    READ_SAMPLE();
+    READ_SAMPLE();
+    READ_SAMPLE();
+    READ_SAMPLE();
+    READ_SAMPLE();
+    READ_SAMPLE();
+    READ_SAMPLE();
+    READ_SAMPLE();
+#endif
+#endif
+    
+    col *= 1.0/r; //divide by sample count
+    
+#if USE_DOF && DEBUG_FOCUS
+        col = debugFocus(col, blur, depth);
+#endif
+    
+    return col;
+}
+
+void main() {
+    out_Color = vec4(calc_dof_mblur(), 1.0); 
+}
+
+#endif
  No newline at end of file

          
A => liminal_legacy/liminal/assets/shaders/g/ppfx/gshader.glsl +167 -0
@@ 0,0 1,167 @@ 
+#include "std/std.glsl"
+#include "view_ray.glsl"
+
+#if FRAGMENT_SHADER
+
+#define M_PI 3.141592653589793
+
+#include "lib/fog.glsl"
+#include "lib/hslhsv.glsl"
+
+#include "gshader_frag.glsl"
+
+#ifndef USE_SSAO
+#define USE_SSAO 0
+#endif
+
+#ifndef DEBUG_SSAO
+#define DEBUG_SSAO 0
+#endif
+
+#ifndef USE_FOG
+#define USE_FOG 0
+#endif
+
+#ifndef USE_IBL
+#define USE_IBL 0
+#endif
+
+#if USE_IBL
+uniform samplerCube ibl_cubemap;
+#endif
+
+#if USE_SSAO
+uniform sampler2D ssao_color0;
+#endif
+
+vec2 uv;
+ivec2 coord;
+GBufferAttributes gba;
+
+uniform vec3 ambient_color = vec3(1.0);
+uniform float ambient_power = 1.0;
+
+const vec3 sky_albedo = vec3(1.0, 1.0, 1.0);
+
+// normal of pixel
+vec3 surface_normal; 
+
+vec3 cubemap_vector(in vec3 v) {
+    return vec3(v.xz, -v.y);
+} 
+
+#define MAX_LOD 7.0
+
+float view_ray_length = length(view_ray);
+vec3 view_dir = view_ray / view_ray_length;
+
+vec3 world_dir = normalize(world_ray);
+
+#if USE_IBL 
+vec3 shade_ibl_cubemap() {
+    return textureLod(ibl_cubemap, cubemap_vector(world_dir), 0.0).rgb * ambient_power;
+}
+
+vec3 brdf_sample_ibl_diffuse(in samplerCube cubemap, in vec3 normal) {
+    return textureLod(cubemap, cubemap_vector(normal), MAX_LOD).rgb;
+}
+
+vec3 brdf_sample_ibl_specular(in samplerCube cubemap, float roughness, in vec3 reflect) {
+    // LOD 10 ~= specular roughness 0.8
+    float a = roughness;
+    a = a / 0.8; 
+
+    // specular IBL
+    float lod = sqrt(a);
+    
+    lod = max(lod*MAX_LOD, 0.0);
+    
+    return textureLod(cubemap, cubemap_vector(reflect), lod).rgb;
+} 
+
+#include "lib/ibl_sample.glsl"
+#endif
+
+vec3 shade_surface() {
+    if (gba.sky == 1.0) {
+#if USE_IBL
+        return shade_ibl_cubemap();
+#else
+        return ambient_color * ambient_power;
+#endif
+    }
+    float roughness = gba.roughness;    
+    float metallic = gba.metallic;
+#if USE_SSAO    
+    // ambient occlusion term
+    float ssao_power = texture(ssao_color0, uv).r;
+#else
+    float ssao_power = 1.0;
+#endif
+    ssao_power *= gba.ao;
+#if !USE_IBL
+    return gba.albedo * ambient_color * (ambient_power * ssao_power / M_PI);
+#else
+    vec3 ibl_diff;
+    vec3 ibl_spec;
+    vec3 n = normalize(mat3(mtx_camera) * gba.normal);      
+#if VIDEO_QUALITY >= VQ_LOWER
+    // setup specular+diffuse IBL
+    ibla = IBLAttributes(
+        gba.metallic,
+        gba.roughness,
+        gba.albedo,
+        -world_dir,
+        n
+    );
+    // diffuse IBL      
+    ibl_diff = vec3(0.0);
+    ibl_spec = ibl_sample_specular_diffuse(ibl_cubemap, ibl_diff);
+#else
+    // diffuse IBL
+    ibl_diff = brdf_sample_ibl_diffuse(ibl_cubemap, n) / M_PI; 
+    ibl_spec = vec3(0.0);
+#endif
+
+    vec3 color = ibl_diff * (1.0 - metallic) + ibl_spec / mix(M_PI, 1.0, gba.metallic);
+    color *= (ambient_color * ambient_power);
+#if USE_SSAO
+    color *= ssao_power;
+#endif    
+    return color;
+#endif
+}
+
+uniform float rainbow_offset = 0.4;
+
+void main() {
+    uv = screen_texcoord;
+    coord = gbuffer_uv_to_texel(uv);
+    gba = read_gbuffer_DANRMSL(uv, coord);    
+
+    vec3 color;
+#if USE_SSAO && DEBUG_SSAO
+    if (gba.sky == 1.0) {
+        color = shade_ibl_cubemap();
+    } else {
+        // ambient occlusion term
+        color = vec3(texture(ssao_color0, uv).r) * 0.5;
+    }
+#else
+    color = shade_surface();
+
+    // add light map
+    color = color * (1.0 - gba.light.a) + gba.light.rgb;
+
+#if USE_FOG
+    // add fog
+    float fog_intensity = get_fog_intensity(vec3(0.0), mtx_camera[3].xyz, 
+        world_dir, view_ray_length * gba.depth);
+    color = mix_fog(color, fog_intensity, world_dir);
+#endif
+#endif
+
+    out_Color = vec4(color, 1.0);
+}
+
+#endif
  No newline at end of file

          
A => liminal_legacy/liminal/assets/shaders/g/ppfx/gshader_frag.glsl +99 -0
@@ 0,0 1,99 @@ 
+
+#ifndef USE_VIEW_NORMAL
+#define USE_VIEW_NORMAL 1
+#endif
+
+uniform sampler2D fb_color0; // albedo, uvlight
+uniform sampler2D fb_color1; // matid
+uniform sampler2D fb_color2; // normal
+uniform sampler2D fb_color3; // light
+//uniform isampler2D fb_color4; // velocity
+uniform vec2 fb_texel;
+uniform vec2 fb_size;
+
+#include "lib/normal_codec.glsl"
+#define GBA_USE_DEPTH 1
+#include "g/gbuffer_attr.glsl"
+
+uniform sampler2D fb_depth; // depth
+
+float linear_z(float z) {
+    float zn = 2.0*z-1.0;
+    float zd = (zn * mtx_proj[3][3] - mtx_proj[3][2]) / (zn * mtx_proj[2][3] - mtx_proj[2][2]);
+    return zd / far;
+}
+
+float fetch_linear_z(ivec2 coord) {
+    return linear_z(texelFetch(fb_depth, coord, 0).r); 
+}
+
+float read_linear_z(vec2 uv) {
+    return linear_z(texture(fb_depth, uv).r); 
+}
+
+vec3 view_normal(vec3 normal) {
+#if USE_VIEW_NORMAL
+    return mat3(mtx_view) * normal;
+#else
+    return normal;
+#endif
+}
+
+ivec2 gbuffer_uv_to_texel(vec2 uv) {
+    return ivec2(uv * fb_size);
+}
+
+GBufferAttributes read_gbuffer_DANRMS(vec2 uv, ivec2 coord) {
+    vec4 color = texelFetch(fb_color0, coord, 0);
+    vec2 fx = texelFetch(fb_color1, coord, 0).rg;
+    vec2 w_norm = texelFetch(fb_color2, coord, 0).rg;
+    float depth = fetch_linear_z(coord); 
+    
+    return GBufferAttributes(
+        depth,
+        color.rgb, // albedo
+        color.a, // ao
+        vec2(0.0), // velocity, usually not required
+        view_normal(decode_normal(w_norm.rg)), // normal
+        fx.r, fx.g, // roughness, metallic,
+        step(1.0, depth), // sky,
+        vec4(0.0) // light
+    );
+}
+
+GBufferAttributes read_gbuffer_DANRMSL(vec2 uv, ivec2 coord) {
+    vec4 color = texelFetch(fb_color0, coord, 0);
+    vec2 fx = texelFetch(fb_color1, coord, 0).rg;
+    vec2 w_norm = texelFetch(fb_color2, coord, 0).rg;
+    float depth = fetch_linear_z(coord); 
+    vec4 light = texelFetch(fb_color3, coord, 0);
+    
+    return GBufferAttributes(
+        depth,
+        color.rgb, // albedo
+        color.a, // ao
+        vec2(0.0), // velocity, usually not required
+        view_normal(decode_normal(w_norm.rg)), // normal
+        fx.r, fx.g, // roughness, metallic,
+        step(1.0, depth), // sky,
+        light // light
+    );
+}
+
+float read_gbuffer_DN(in ivec2 coord, out vec3 normal) {
+    vec2 w_norm = texelFetch(fb_color2, coord, 0).rg;
+    normal = view_normal(decode_normal(w_norm.rg));
+    return fetch_linear_z(coord);
+}
+
+float fetch_gbuffer_D(ivec2 coord) {
+    return fetch_linear_z(coord);
+}
+
+float read_gbuffer_D(vec2 uv) {
+    return read_linear_z(uv);
+}
+
+vec3 read_gbuffer_N(ivec2 coord) {
+    return view_normal(decode_normal(texelFetch(fb_color2, coord, 0).rg));
+}

          
A => liminal_legacy/liminal/assets/shaders/g/ppfx/light_direct.glsl +308 -0
@@ 0,0 1,308 @@ 
+#include "std/std.glsl"
+
+#define VIEW_RAY_MAIN view_ray_main
+#include "view_ray.glsl"
+
+// light dir in view space
+flat varying vec3 light_dir_vs;
+
+#if VERTEX_SHADER
+
+uniform vec3 light_dir;
+
+void main(void) {
+    view_ray_main();
+    light_dir_vs = normalize(mat3(mtx_view) * light_dir);
+}
+
+#elif FRAGMENT_SHADER
+
+#include "gshader_frag.glsl"
+#include "lib/brdf.glsl"
+#include "lib/evsm.glsl"
+
+#if VIDEO_QUALITY < VQ_LOW
+#undef USE_VLS
+#define USE_VLS 0
+#endif
+
+#if USE_VLS
+#if VIDEO_QUALITY >= VQ_HIGHER
+#define VLS_STEPS 16
+#elif VIDEO_QUALITY >= VQ_MEDIUM
+#define VLS_STEPS 8
+#else
+#define VLS_STEPS 4
+#endif
+#define ABORT return
+const float vls_factor = 1.0/1.0;
+const float vls_density = 1.0/250.0;
+#else
+#define ABORT discard
+#endif
+
+#if EVSM_ENABLE
+#define TEXTURE_EVSM texture_evsm
+#else // EVSM_ENABLE
+#define TEXTURE_EVSM texture_vsm
+#endif // EVSM_ENABLE
+
+#ifndef USE_SSAO
+#define USE_SSAO 0
+#endif
+
+#ifndef USE_AMBIENT
+#define USE_AMBIENT 0
+#endif
+
+#if DEBUG_CASCADE
+#define LIGHT_COLOR vec3(1.0)
+#define ALBEDO_COLOR vec3(1.0)
+#define SHADOW_TYPE vec3
+#else
+#define SHADOW_TYPE float
+#define LIGHT_COLOR light_color
+#define ALBEDO_COLOR gba.albedo
+#endif
+
+#if USE_SHADOW
+#define SHADOW_FACTOR(X) lit * (X)
+#else
+#define SHADOW_FACTOR(X) X
+#endif
+
+uniform sampler2DArray dm_color0;
+uniform mat4 dm_projview_mtx[LAYER_COUNT];
+uniform vec3 dm_split_coeffs;
+uniform float dm_rolloff = 1.0;
+uniform vec3 light_color = vec3(1.0);
+uniform float light_power = 1.0;
+uniform vec3 light_pos;
+uniform vec2 dm_range;
+
+#if USE_SSAO
+uniform sampler2D ssao_color0;
+#endif
+
+/*
+float texture_vsm(in sampler2DArray smp, in vec4 uv) {
+    float depth = uv.z;
+    vec3 pos_dx = dFdx(uv.xyz);
+    vec3 pos_dy = dFdy(uv.xyz);
+    
+    vec2 moments = textureGrad(smp, uv.xyw, pos_dx.xy, pos_dy.xy).rg;
+    
+    return chebyshev_upper_bound(moments, depth, min_variance, light_bleeding_bias); 
+}
+*/
+
+float texture_evsm(in sampler2DArray smp, in vec4 uv) {
+    float depth = uv.z;
+    vec3 pos_dx = dFdx(uv.xyz);
+    vec3 pos_dy = dFdy(uv.xyz);
+    
+    vec2 warped_depth = warp_depth(depth, exponents);
+
+    vec4 moments = textureGrad(smp, uv.xyw, pos_dx.xy, pos_dy.xy);
+    
+    // Derivative of warping at depth
+    vec2 depthScale = min_variance * exponents * warped_depth;
+    vec2 minVariance = depthScale * depthScale;
+    
+#if EVSM4
+    float posContrib = chebyshev_upper_bound(moments.xz, warped_depth.x, minVariance.x, light_bleeding_bias);
+    float negContrib = chebyshev_upper_bound(moments.yw, warped_depth.y, minVariance.y, light_bleeding_bias);
+    return min(posContrib, negContrib);
+#else
+    // Positive only
+    return chebyshev_upper_bound(moments.xy, warped_depth.x, minVariance.x, light_bleeding_bias); 
+#endif    
+}
+
+float sample_shadow_layer_hard(in float layer, in vec3 p) {
+    int i = int(layer);
+    vec4 uv = dm_projview_mtx[i] * vec4(p, 1.0);
+    float d = warp_depth1(uv.z, exponents);
+    float z = texture(dm_color0, vec3(uv.xy, layer)).r;
+    return step(d, z);
+    //return clamp((z - d)*1000.0, 0.0, 1.0);
+}
+
+SHADOW_TYPE sample_shadow_layer(in float layer, in vec3 p) {
+    int i = int(layer);
+    vec4 uv = dm_projview_mtx[i] * vec4(p, 1.0);
+    
+#if DEBUG_CASCADE
+    float depth = TEXTURE_EVSM(dm_color0, vec4(uv.xyz, layer));
+    vec3 layer_color;
+    switch(i % 6) {
+        case 0: layer_color = vec3(1.0, 0.2, 0.2); break;
+        case 1: layer_color = vec3(1.0, 1.0, 0.2); break;
+        case 2: layer_color = vec3(0.2, 1.0, 0.2); break;
+        case 3: layer_color = vec3(0.2, 1.0, 1.0); break;
+        case 4: layer_color = vec3(0.2, 0.2, 1.0); break;
+        case 5: layer_color = vec3(1.0, 0.2, 1.0); break;
+        default: layer_color = vec3(1.0, 1.0, 1.0); break;
+    }
+    return layer_color * mix(0.1, 1.0, depth);
+#else
+    return TEXTURE_EVSM(dm_color0, vec4(uv.xyz, layer));
+#endif
+}
+
+SHADOW_TYPE sample_shadow_cascade(in vec3 p, in float z) {
+    float l = log2(z * dm_split_coeffs.x + dm_split_coeffs.y) * dm_split_coeffs.z;
+    float l0 = floor(l);
+    
+    float w = max(0.0, (l - l0 + dm_rolloff - 1.0) / dm_rolloff);
+    SHADOW_TYPE lit = sample_shadow_layer(l0, p);
+    SHADOW_TYPE lit2 = sample_shadow_layer(l0+1.0, p);
+    if (w > 0.0) {
+        lit = mix(lit, lit2, w);
+    }
+    return lit;
+}
+
+float sample_shadow_cascade_hard(in vec3 p, in float z) {
+    float l = log2(z * dm_split_coeffs.x + dm_split_coeffs.y) * dm_split_coeffs.z;
+    return sample_shadow_layer_hard(floor(l), p);
+}
+
+float rand(vec3 co){
+    return fract(sin(dot(co.xyz,vec3(12.9898,78.233,91.1743))) * 43758.5453);
+}
+
+float light_falloff(float d, float radius) {
+    float dmlr = d / radius;
+    float dmlr2 = dmlr*dmlr;
+    float falloff_n = clamp(1.0 - dmlr2*dmlr2, 0.0, 1.0);
+    return ((falloff_n*falloff_n) / (d*d+1.0));
+}
+
+#if USE_VLS
+vec4 gather_scatter_volume(float depth, vec2 uv) {
+    float scatter = 0.0;
+    float dlin = min(0.5, depth); //0.5;
+    float vls_z = -view_ray.z * dlin;// * depth;
+    vec3 vls_ray = world_ray * dlin;// * depth;
+    float max_z = -view_ray.z * depth;
+    
+    vec3 o = mtx_camera[3].xyz;
+    
+    float t_ofs = mod(time,1.0);
+    float vls_depth, z, d, w;
+    vec3 p;
+    float div_steps = 1.0 / float(VLS_STEPS);
+    float d_ofs = abs(rand(vec3(uv.xy, t_ofs))) * div_steps; 
+
+#define GATHER_RAY(i) \
+    vls_depth = d_ofs + i * div_steps; \
+    p = o + vls_ray * vls_depth; \
+    z = vls_z * vls_depth; \
+    d = z * vls_density; \
+    scatter += sample_shadow_cascade_hard(p, z) \
+        * light_falloff(length(p - light_pos) / dm_range.y, 1.0);
+    
+    GATHER_RAY(0.0);
+    GATHER_RAY(1.0);
+    GATHER_RAY(2.0);
+    GATHER_RAY(3.0);
+#if VLS_STEPS > 4
+    GATHER_RAY(4.0);
+    GATHER_RAY(5.0);
+    GATHER_RAY(6.0);
+    GATHER_RAY(7.0);
+#if VLS_STEPS > 8
+    GATHER_RAY(8.0);
+    GATHER_RAY(9.0);
+    GATHER_RAY(10.0);
+    GATHER_RAY(11.0);
+    GATHER_RAY(12.0);
+    GATHER_RAY(13.0);
+    GATHER_RAY(14.0);
+    GATHER_RAY(15.0);
+#endif
+#endif
+    d = vls_z * vls_density;
+    scatter *= vls_factor * div_steps * (1.0 - exp(-d*d));
+    return scatter * vec4(LIGHT_COLOR, 1.0);
+}
+#endif
+
+void main() {
+    vec2 uv = screen_texcoord;
+    vec3 view_dir = normalize(view_ray);
+    
+    ivec2 coord = gbuffer_uv_to_texel(uv);
+    
+    // retrieve pixel data
+    GBufferAttributes gba = read_gbuffer_DANRMS(uv, coord);
+
+#if USE_VLS
+    out_Color = gather_scatter_volume(gba.depth, uv);
+#endif
+
+    vec3 p = mtx_camera[3].xyz + world_ray * gba.depth;
+    float z = -view_ray.z * gba.depth;
+#if USE_SHADOW    
+    SHADOW_TYPE lit = sample_shadow_cascade(p, z);
+#else
+    SHADOW_TYPE lit = 1.0;
+#endif
+
+    if (gba.sky == 1.0)
+        ABORT;
+
+    float cos_Ol = dot(gba.normal,light_dir_vs);
+
+#if USE_AMBIENT
+    float ambientf = (1.0 - gba.metallic) * mix(0.01, 0.1, max(0.0,-cos_Ol)); 
+#if USE_SSAO
+    float ssao_factor = texture(ssao_color0, uv).r;
+#if USE_SHADOW
+    ambientf *= ssao_factor;
+#endif
+#endif
+        
+    // ambient bounce
+    vec3 color = ALBEDO_COLOR * ambientf;
+#else // USE_AMBIENT
+    vec3 color = vec3(0.0);
+#endif
+        
+    if (cos_Ol > 0.0) {
+        cos_Ol = max(0.0, cos_Ol);
+#if USE_SHADOW && !DEBUG_CASCADE
+        if (lit > 0.0) {
+#endif // USE_SHADOW
+    
+            brdfa = BRDFAttributes(
+                gba.metallic,
+                gba.roughness,
+                ALBEDO_COLOR,
+                cos_Ol,
+                light_dir_vs,
+                -view_dir,
+                gba.normal
+            ); 
+            
+            color += brdf_evaluate() * (SHADOW_FACTOR(cos_Ol));
+#if USE_SHADOW && !DEBUG_CASCADE
+        }
+#endif
+    }
+
+#if USE_SSAO && !USE_SHADOW
+    color *= LIGHT_COLOR * light_power * ssao_factor;
+#else    
+    color *= LIGHT_COLOR * light_power;
+#endif
+
+#if USE_VLS
+    out_Color = out_Color + vec4(color*(1.0 - out_Color.a), 0.0);
+#else    
+    out_Color = vec4(color, 0.0);
+#endif
+}
+
+#endif
  No newline at end of file

          
A => liminal_legacy/liminal/assets/shaders/g/ppfx/light_proj.glsl +188 -0
@@ 0,0 1,188 @@ 
+#include "std/std.glsl"
+
+#ifndef USE_SPOT
+#define USE_SPOT 0
+#endif
+
+#ifndef USE_ATTENUATION
+#define USE_ATTENUATION 1
+#endif
+
+#ifndef USE_BRDF
+#define USE_BRDF 1
+#endif
+
+#ifndef DEBUG_LIGHT
+#define DEBUG_LIGHT 1
+#endif
+
+#ifndef USE_TEXTURE
+#define USE_TEXTURE 0
+#endif
+
+uniform float light_radius = 1.0;
+#if USE_SPOT
+uniform mat4 dm_pv_mtx;
+uniform mat4 dm_pv_inv_mtx;
+#endif
+
+// light position in view space
+flat varying vec3 light_position_vs;
+noperspective varying vec2 window_xy;
+noperspective varying vec2 screen_texcoord;
+
+#if VERTEX_SHADER
+
+uniform vec3 light_position;
+
+void main(void) {
+    vec4 position = in_Position;
+#if USE_SPOT
+    position.z *= -1.0;
+    position = (dm_pv_inv_mtx * position);
+    position = position / position.w;
+    gl_Position = mtx_proj * (mtx_view * position);
+#else    
+    position.xyz = position.xyz * light_radius;
+    gl_Position = mtx_proj * (mtx_view * (mtx_model * position));
+#endif
+    light_position_vs = (mtx_view * vec4(light_position,1.0)).xyz;
+    
+    window_xy = gl_Position.xy / gl_Position.w;
+    vec2 absolute_texcoord = window_xy * 0.5 + 0.5;
+    screen_texcoord = absolute_texcoord * viewport_scale + viewport_offset;
+}
+
+#elif FRAGMENT_SHADER
+
+#define M_PI 3.141592653589793
+
+#include "gshader_frag.glsl"
+#if USE_BRDF    
+#include "lib/brdf.glsl"
+#endif
+
+#if USE_TEXTURE
+#if USE_CUBEMAP
+uniform samplerCube light_texture;
+#else
+uniform sampler2D light_texture;
+#endif // USE_CUBEMAP
+#endif // USE_TEXTURE
+uniform vec3 light_color = vec3(1.0);
+uniform float light_power = 1.0;
+
+float light_falloff(float d, float radius) {
+    float dmlr = d / radius;
+    float dmlr2 = dmlr*dmlr;
+    float falloff_n = clamp(1.0 - dmlr2*dmlr2, 0.0, 1.0);
+    return ((falloff_n*falloff_n) / (d*d+1.0));
+}
+
+/*
+// L = vector from surface position to light center
+// n = normal
+// radius = radius of light sphere
+vec3 sphere_light_l(vec3 L, vec3 v, vec3 n, float radius) {
+    vec3 r = reflect(v, n);
+    vec3 ray_center = L - dot(L, r)*r;
+    vec3 closest = L + ray_center * clamp(radius / length(ray_center), 0.0, 1.0);
+    return normalize(closest);
+} 
+*/
+
+void main() {
+    vec2 uv = screen_texcoord;
+    ivec2 coord = gbuffer_uv_to_texel(uv);
+    
+    // retrieve pixel data
+    GBufferAttributes gba = read_gbuffer_DANRMS(uv, coord);   
+    //if (gba.sky == 1.0) {
+    //    discard;
+    //}
+    
+    vec4 far_pos = mtx_inv_proj * vec4(window_xy, 1.0, 1.0);
+    far_pos /= far_pos.w;
+    vec3 view_ray = far_pos.xyz;
+    
+    vec3 surface_pos = view_ray * gba.depth;
+    
+    vec3 n = gba.normal;
+    vec3 v = -normalize(view_ray);
+    vec3 p = light_position_vs - surface_pos;
+    vec3 l = normalize(p);
+    
+    float lit = light_power;
+
+#if USE_SPOT
+    vec4 world_pos = mtx_camera * vec4(surface_pos, 1.0);
+    vec4 spot_proj = (dm_pv_mtx * world_pos);
+    vec3 spot_uv = (spot_proj.xyz / spot_proj.w);    
+    vec3 spot_range = abs(spot_uv);
+#define LIGHT_COLOR_OP(X) (spot_color*X)
+#if USE_TEXTURE
+    vec3 spot_color = textureLod(light_texture, spot_uv.xy*0.5+0.5, 0.0).rgb;
+#else
+    float cone_range = length(spot_range.xy);
+    vec3 spot_color = vec3(clamp((cone_range - 1.0) / (0.9 - 1.0), 0.0, 1.0));
+#endif
+#if DEBUG_LIGHT
+    lit *= step(max(max(spot_range.x,spot_range.y),spot_range.z),1.0);
+#endif
+#else // !USE_SPOT
+#if USE_TEXTURE
+#define LIGHT_COLOR_OP(X) (point_color*X)
+    vec3 wl = mat3(mtx_model) * (mat3(mtx_camera) * l);
+#if USE_CUBEMAP
+    vec3 point_color = textureLod(light_texture, wl, 0.0).rgb;
+#else    
+    vec3 point_color = textureLod(light_texture, abs(wl*0.5 + 0.5).xy, 0.0).rgb;
+#endif // USE_CUBEMAP 
+#else
+#define LIGHT_COLOR_OP(X) X
+#endif // USE_TEXTURE
+#endif // !USE_SPOT
+
+#if USE_ATTENUATION
+    float distance = length(p);
+    lit *= light_falloff(distance, light_radius);                             
+#endif
+
+#if !DEBUG_LIGHT
+    if (lit == 0.0)
+        discard;
+#endif
+
+#if USE_BRDF
+
+    float cos_Ol = max(dot(n,l),0.0);
+    
+    brdfa = BRDFAttributes(
+        gba.metallic,
+        gba.roughness,
+        gba.albedo,
+        cos_Ol,
+        l,
+        v,
+        n
+    ); 
+    
+    lit *= cos_Ol;
+    out_Color.rgba = vec4((LIGHT_COLOR_OP(light_color) * lit) * brdf_evaluate(),0.0);
+    
+#else // !USE_BRDF
+
+    out_Color.rgba = vec4((LIGHT_COLOR_OP(light_color) * lit),0.0);
+
+#endif // USE_BRDF    
+    
+#if DEBUG_LIGHT
+#if USE_SPOT
+    out_Color += vec4(1.0,0.0,0.0,0.0);
+#else
+    out_Color += vec4(0.0,0.0,1.0,0.0);
+#endif    
+#endif
+}
+
+#endif
  No newline at end of file

          
A => liminal_legacy/liminal/assets/shaders/g/ppfx/light_shgrid.glsl +238 -0
@@ 0,0 1,238 @@ 
+#include "std/std.glsl"
+
+#define USE_VIEW_NORMAL 0
+#define USE_VIEW_RAY_MODEL 1
+#define VIEW_RAY_MAIN view_ray_main
+#include "view_ray.glsl"
+
+#ifndef USE_SSAO
+#define USE_SSAO 0
+#endif
+
+#ifndef DEBUG_GRID
+#define DEBUG_GRID 0
+#endif
+
+#if VERTEX_SHADER
+
+void main(void) {
+    view_ray_main();
+}
+
+#elif FRAGMENT_SHADER
+
+#include "gshader_frag.glsl"
+#include "lib/sh.glsl"
+#include "lib/math.glsl"
+
+//uniform sampler3D occluders;
+uniform sampler2D shgrid;
+uniform int igridsize;
+
+#if USE_SSAO
+uniform sampler2D ssao_color0;
+#endif
+
+uniform vec3 light_color = vec3(1.0);
+uniform float light_power = 1.0;
+
+uniform float test_bias = 0.5;
+float gridsize = float(igridsize); 
+float invgridsize = 1.0/gridsize;
+float halfinvgridsize = invgridsize*0.5;
+
+vec3 cubemap_vector(in vec3 v) {
+    return vec3(v.xz, -v.y);
+} 
+
+vec4 texture_fetch_clamp(sampler2D s, ivec3 p, int channel) {
+    float pmn = float(min(p.x,min(p.y,p.z)));
+    float pmx = float(max(p.x,max(p.y,p.z)));
+
+    float f = step(0.0, pmn) * 
+        step(pmx, gridsize-1.0);
+    return texelFetch(s, ivec2(p.x+p.y*igridsize, 
+        p.z+channel*igridsize), 0) * f;
+
+}
+
+float shade_probe(vec4 sh, vec4 shn) {
+    return sh_shade(sh_irradiance_probe(sh), shn);
+}
+
+float texture_fetch_2d_as_3d(sampler2D s, ivec3 ip, 
+    vec3 mp, vec4 shn, int c) {
+    
+    ivec2 o = ivec2(0, 1);    
+    float sh[8];
+    sh[0] = shade_probe(texture_fetch_clamp(shgrid, ip + o.xxx, c), shn);
+    sh[1] = shade_probe(texture_fetch_clamp(shgrid, ip + o.yxx, c), shn);
+    sh[2] = shade_probe(texture_fetch_clamp(shgrid, ip + o.xyx, c), shn);
+    sh[3] = shade_probe(texture_fetch_clamp(shgrid, ip + o.yyx, c), shn);
+    sh[4] = shade_probe(texture_fetch_clamp(shgrid, ip + o.xxy, c), shn);
+    sh[5] = shade_probe(texture_fetch_clamp(shgrid, ip + o.yxy, c), shn);
+    sh[6] = shade_probe(texture_fetch_clamp(shgrid, ip + o.xyy, c), shn);
+    sh[7] = shade_probe(texture_fetch_clamp(shgrid, ip + o.yyy, c), shn);
+    
+    return mix(
+    mix(mix(sh[0],sh[1],mp.x),
+        mix(sh[2],sh[3],mp.x),mp.y),
+    mix(mix(sh[4],sh[5],mp.x),
+        mix(sh[6],sh[7],mp.x),mp.y),
+    mp.z);
+}
+
+vec3 shade_grid(sampler2D shgrid, vec3 p, vec4 shn) {
+    p *= gridsize;    
+    p -= 0.5;
+    ivec3 ip = ivec3(p);
+    p -= floor(p);
+    
+    p = smoothstep(0.0, 1.0, p);
+    
+    return vec3(
+        texture_fetch_2d_as_3d(shgrid, ip, p, shn, 0),
+        texture_fetch_2d_as_3d(shgrid, ip, p, shn, 1),
+        texture_fetch_2d_as_3d(shgrid, ip, p, shn, 2));
+}
+
+#if DEBUG_GRID
+
+float map_sphere_grid(vec3 p, float d) {
+    float radius = d / 8.0;
+    p = mod(p + 0.5*d, d) - 0.5*d;
+    return length(p) - radius;
+}
+
+float map(vec3 p) {
+    return map_sphere_grid(p, 10.0/64.0);
+}
+
+vec3 map_gradient(vec3 p) {
+    vec2 d = vec2(1e-5, 0.0);
+    return normalize(vec3(
+        map(p + d.xyy) - map(p - d.xyy),
+        map(p + d.yxy) - map(p - d.yxy),
+        map(p + d.yyx) - map(p - d.yyx)));
+}
+
+float trace_sphere_grid(vec3 ro, vec3 rd, float maxt) {
+    float h = 0.0;
+    float t = 0.0;
+    for (int i=0; i<128; i++) {
+        h = map(ro + t*rd);
+        if (abs(h) < 1e-5) return t;
+        t += h;
+        if (t > maxt) return -1.0f; 
+    }
+    return -1.0f;    
+}
+
+#endif
+
+void main() {
+    vec2 uv = screen_texcoord;
+    vec3 view_dir = normalize(view_ray);
+    
+    ivec2 coord = gbuffer_uv_to_texel(uv);
+    
+    // retrieve pixel data
+    GBufferAttributes gba = read_gbuffer_DANRMS(uv, coord);
+
+    //vec3 p = mtx_camera[3].xyz + world_ray * gba.depth;
+    vec3 p = model_origin + world_ray * gba.depth;
+    //float z = -view_ray.z * gba.depth;
+    
+    p = p*0.5 + 0.5;
+    
+    
+    vec3 base_light_power = light_power * light_color; 
+
+
+    vec4 shn;
+    shn = sh_project(-gba.normal);
+
+    vec3 lc;
+    if (true) { //uv.y > 0.5) {
+        lc = shade_grid(shgrid, p, shn);
+    } else {
+        lc = vec3(1.0);
+    }
+
+#if DEBUG_GRID // visualize shgrid
+    vec3 rd = normalize(world_ray); 
+    float t = trace_sphere_grid(model_origin, rd, length(world_ray) * gba.depth);
+    if (t >= 0.0f) {
+        p = model_origin + rd*t;
+        vec3 n = map_gradient(p);
+        if (true) //uv.x < 0.5f) // true: surf response, false: light direction
+            shn = sh_project(-n);
+        else
+            shn = sh_project(n);
+        
+        p = p*0.5 + 0.5;
+        out_Color = vec4(base_light_power * 
+            shade_grid(shgrid, p, shn), 1.0);
+        return;
+    }
+#endif
+
+    lc *= gba.albedo;  
+
+#if USE_SSAO
+    float lum = dot(lc, vec3(0.2126, 0.7152, 0.0722));
+    float ssao = texture(ssao_color0, uv).r;
+    lc = max(vec3(0.0), lc - (1.0 - ssao)*lum*0.5);
+    lc *= lum + (1.0 - lum)*ssao;
+#endif
+
+    out_Color = vec4(base_light_power * lc, 0.0);
+        
+    //out_Color = vec4(gba.albedo, 0.0);
+
+#if 0
+    vec3 color = vec3(0.0);
+    vec3 v = -normalize(world_ray);
+    vec3 n = gba.normal;
+    vec3 d = reflect(-v, n);
+
+    vec3 l = d;
+    
+    vec3 h = normalize(l+v);
+    
+    float cos_Ol = max(0.0, dot(n, l));
+    float cos_Oh = max(0.0, dot(n, h));
+    float cos_Od = max(0.0, dot(v, h));
+    float cos_Ov = max(0.0, dot(n, v));
+    
+    float r = gba.roughness; 
+    float k = (r*r) / 2.0;
+    float ik = 1.0 - k;    
+    float Gnlvh = cos_Ol;
+    float Gdlvh = (cos_Ov*ik + k) * (cos_Ol*ik + k);
+    float G = (Gnlvh / Gdlvh);
+    
+    vec3 specular_color = mix(vec3(1.0), gba.albedo, gba.metallic);
+    
+    float Fc = pow(1.0 - cos_Od, 5.0);
+    vec3 F = (1.0 - Fc) * specular_color + vec3(Fc);
+    float fs = 0.3 * G * cos_Od / cos_Oh;
+    
+#if 0
+    out_Color.rgb = d*0.5+0.5;
+#else
+    shn = sh_project(-d);
+    float s = 0.0;
+    float t = 1.0;
+    for (int i = 0; i < 4; ++i) {
+        color += shade_grid(shgrid, p + d*t*invgridsize, shn)/(t*t);
+        t += 1.0;
+    }
+    out_Color.rgb += 8.0 * (color/4.0) * clamp(F*fs,0.0,1.0); //clamp(F * fs, 0.0, 1.0);
+#endif
+    out_Color.a = 0.0;
+#endif    
+    
+}
+
+#endif
  No newline at end of file

          
A => liminal_legacy/liminal/assets/shaders/g/ppfx/ssao.glsl +112 -0
@@ 0,0 1,112 @@ 
+#include "std/std.glsl"
+#include "view_ray.glsl"
+
+#if FRAGMENT_SHADER
+
+#include "gshader_frag.glsl"
+
+uniform float radius = 0.03;
+
+#include "lib/math.glsl"
+
+#if VIDEO_QUALITY >= VQ_HIGHEST
+#define SAMPLE_KERNEL_COUNT 64
+#define HAMMERSLEY_DEPTH 128
+#elif VIDEO_QUALITY >= VQ_HIGHER
+#define SAMPLE_KERNEL_COUNT 16
+#define HAMMERSLEY_DEPTH 24
+#else
+#define SAMPLE_KERNEL_COUNT 8
+#define HAMMERSLEY_DEPTH 24
+#endif
+
+#include "lib/hammersley.glsl"
+#define HAMMERSLEY_DEPTH_U uint(HAMMERSLEY_DEPTH)
+int h_index;
+const float h_scale = 1.0 / float(SAMPLE_KERNEL_COUNT);
+
+vec3 origin;
+mat3 tbn;
+
+float read_sample(int i) {
+    // get sample position:
+    vec2 h = hammersley2d(uint(i + h_index), HAMMERSLEY_DEPTH_U);
+    vec3 s = tbn * (
+        hemisphere_sample_uniform(h.x, h.y) * (
+        h_scale * float(1+i)));
+    s = s * radius + origin;
+    
+    // project sample position:
+    vec4 offset = vec4(s, 1.0);
+    offset = mtx_proj * offset;
+    offset.xy /= offset.w;
+    offset.xy = offset.xy * 0.5 + 0.5;
+    
+    // get sample depth:
+    float sample_depth = read_gbuffer_D(
+        offset.xy * viewport_scale + viewport_offset);
+    sample_depth *= -view_ray.z;
+    
+    // range check & accumulate:
+    float dist = abs(origin.z - sample_depth);
+    float range_check = clamp(2.0 - dist/radius, 0.0, 1.0);
+    return step(sample_depth, s.z) * range_check;
+}
+
+void main() {
+    vec2 uv = screen_texcoord;
+    ivec2 coord = gbuffer_uv_to_texel(uv);
+    
+    vec3 normal;
+    float depth = read_gbuffer_DN(coord, normal);
+    if (depth == 1.0) {
+        out_Color.r = 1.0;
+        return;
+    }
+    
+    origin = depth * -view_ray;
+    normal *= -1.0;
+    
+    float k;
+    k = random(vec3(uv,mod(time,1.0)));
+
+    h_index = int(k*10000.0) % (
+        HAMMERSLEY_DEPTH-SAMPLE_KERNEL_COUNT);
+    
+    vec3 rvec = vec3(cos(k), sin(k), 0.0);
+    vec3 tangent = normalize(rvec - normal * dot(rvec, normal));
+    vec3 bitangent = cross(normal, tangent);
+    tbn = mat3(tangent, bitangent, normal);
+
+#if SAMPLE_KERNEL_COUNT > 16
+    float occlusion = 0.0;
+    for (int i = 0; i < SAMPLE_KERNEL_COUNT; ++i) {
+        occlusion += read_sample(i);
+    } 
+#else    
+    float occlusion = read_sample(0);
+    occlusion += read_sample(1);
+    occlusion += read_sample(2);
+    occlusion += read_sample(3);
+    occlusion += read_sample(4);
+    occlusion += read_sample(5);
+    occlusion += read_sample(6);
+    occlusion += read_sample(7);
+#if SAMPLE_KERNEL_COUNT > 8    
+    occlusion += read_sample(8);
+    occlusion += read_sample(9);
+    occlusion += read_sample(10);
+    occlusion += read_sample(11);
+    occlusion += read_sample(12);
+    occlusion += read_sample(13);
+    occlusion += read_sample(14);
+    occlusion += read_sample(15);
+#endif
+#endif
+    
+    occlusion = 1.0 - (occlusion * h_scale);
+    
+    out_Color.r = occlusion;
+}
+
+#endif
  No newline at end of file

          
A => liminal_legacy/liminal/assets/shaders/g/ppfx/ssgi.glsl +136 -0
@@ 0,0 1,136 @@ 
+// a naive attempt at screen-space global illumination
+
+#include "std/std.glsl"
+#include "view_ray.glsl"
+
+#if FRAGMENT_SHADER
+
+#ifndef SSAO_DEBAND
+#define SSAO_DEBAND 1
+#endif
+
+#ifndef SSAO_HAMMERSLEY
+#define SSAO_HAMMERSLEY 1
+#endif
+
+#include "gshader_frag.glsl"
+
+#if SSAO_DEBAND
+float rand(vec3 co){
+    return fract(sin(dot(co.xyz,vec3(12.9898,78.233,91.1743))) * 43758.5453);
+}
+#else
+uniform sampler2D sm_noise;
+uniform vec2 noise_scale;
+#endif
+uniform float radius = 0.03;
+
+uniform sampler2D combined_color0;
+
+#include "lib/snoise3.glsl"
+
+#define SAMPLE_KERNEL_SIZE 16
+#define SAMPLE_KERNEL_COUNT 16
+#if SSAO_HAMMERSLEY
+#include "lib/hammersley.glsl"
+#define HAMMERSLEY_DEPTH 24
+#define HAMMERSLEY_DEPTH_U uint(HAMMERSLEY_DEPTH)
+int h_index;
+const float h_scale = 1.0 / float(SAMPLE_KERNEL_COUNT);
+#else
+uniform vec3 sample_kernel[SAMPLE_KERNEL_SIZE];
+#endif
+
+vec3 origin;
+mat3 tbn;
+
+vec3 read_sample(int i) {
+    // get sample position:
+#if SSAO_HAMMERSLEY
+    vec2 h = hammersley2d(uint(i + h_index), HAMMERSLEY_DEPTH_U);
+    vec3 s = tbn * (
+        hemisphere_sample_uniform(h.x, h.y) * (
+        h_scale * float(1+i)));
+#else
+    vec3 s = tbn * sample_kernel[i];
+#endif
+    s = s * radius + origin;
+    
+    // project sample position:
+    vec4 offset = vec4(s, 1.0);
+    offset = mtx_proj * offset;
+    offset.xy /= offset.w;
+    offset.xy = offset.xy * 0.5 + 0.5;
+    
+    vec2 s_uv = offset.xy * viewport_scale + viewport_offset;
+    
+    // get sample depth:
+    float sample_depth = read_gbuffer_D(s_uv);
+    sample_depth *= -view_ray.z;
+    
+    // range check & accumulate:
+    float dist = abs(origin.z - sample_depth);
+    float range_check = clamp(2.0 - dist/radius, 0.0, 1.0);
+    float f = step(sample_depth, s.z) * range_check;
+    return texture(combined_color0, s_uv).rgb * f; 
+}
+
+void main() {
+    vec2 uv = screen_texcoord;
+    ivec2 coord = gbuffer_uv_to_texel(uv);
+    bool even = ycocg_even(coord);
+    
+    vec3 normal;
+    GBufferAttributes gba = read_gbuffer_DANRMS(uv, coord, even);
+    //float depth = read_gbuffer_DN(coord, normal);
+    float depth = gba.depth;
+    normal = gba.normal;
+    
+    vec3 original = texture(combined_color0, uv).rgb;
+    
+    if (depth == 1.0) {
+        out_Color.rgb = original;
+        return;
+    }
+    
+    origin = depth * -view_ray;
+    normal *= -1.0;
+    
+    float k = rand(vec3(uv,mod(time + SSGI_PASS_ID*3.1415,1.0)));
+    
+#if SSAO_HAMMERSLEY
+    h_index = int(k*10000.0) % (
+        HAMMERSLEY_DEPTH-SAMPLE_KERNEL_COUNT);
+#endif
+    
+#if SSAO_DEBAND
+    vec3 rvec = vec3(cos(k), sin(k), 0.0);
+#else
+    vec3 rvec = vec3(texture(sm_noise, uv * noise_scale).rg, 0.0);
+#endif
+    vec3 tangent = normalize(rvec - normal * dot(rvec, normal));
+    vec3 bitangent = cross(normal, tangent);
+    tbn = mat3(tangent, bitangent, normal);
+
+    vec3 occlusion = read_sample(0);
+    occlusion += read_sample(1);
+    occlusion += read_sample(2);
+    occlusion += read_sample(3);
+    occlusion += read_sample(4);
+    occlusion += read_sample(5);
+    occlusion += read_sample(6);
+    occlusion += read_sample(7);
+    occlusion += read_sample(8);
+    occlusion += read_sample(9);
+    occlusion += read_sample(10);
+    occlusion += read_sample(11);
+    occlusion += read_sample(12);
+    occlusion += read_sample(13);
+    occlusion += read_sample(14);
+    occlusion += read_sample(15);
+    
+    vec3 final = gba.albedo * ((1.0 - gba.metallic) * occlusion * (0.6 / float(SAMPLE_KERNEL_COUNT)));
+    out_Color.rgb = original + final;
+}
+
+#endif
  No newline at end of file

          
A => liminal_legacy/liminal/assets/shaders/g/ppfx/view_ray.glsl +40 -0
@@ 0,0 1,40 @@ 
+noperspective varying vec2 window_xy;
+noperspective varying vec2 absolute_texcoord;
+noperspective varying vec2 screen_texcoord;
+
+varying vec3 view_ray;
+varying vec3 world_ray;
+
+#ifndef USE_VIEW_RAY_MODEL
+#define USE_VIEW_RAY_MODEL 0
+#endif
+
+#ifndef VIEW_RAY_MAIN
+#define VIEW_RAY_MAIN main
+#endif
+
+#if USE_VIEW_RAY_MODEL
+flat varying vec3 model_origin;
+#endif
+
+#if VERTEX_SHADER
+
+void VIEW_RAY_MAIN(void) {
+    gl_Position = vec4(in_Position.xy, 0.0, 1.0);
+    window_xy = in_Position.xy;
+    absolute_texcoord = window_xy * 0.5 + 0.5;
+    screen_texcoord = absolute_texcoord * viewport_scale + viewport_offset;
+    
+    vec4 far_pos = mtx_inv_proj * vec4(in_Position.xy, 1.0, 1.0);
+    far_pos /= far_pos.w;    
+    view_ray = far_pos.xyz;
+    
+    
+    world_ray = mat3(mtx_camera) * view_ray;
+#if USE_VIEW_RAY_MODEL
+    world_ray = mat3(mtx_inv_model) * world_ray;
+    model_origin = (mtx_inv_model * mtx_camera[3]).xyz;
+#endif
+}
+
+#endif

          
A => liminal_legacy/liminal/assets/shaders/g/skybox.glsl +30 -0
@@ 0,0 1,30 @@ 
+
+#include "std/std.glsl"
+
+#include "gshader_vars.glsl"
+
+#if VERTEX_SHADER
+
+void main(void) {
+    gl_Position = vec4(in_Position.xy, 0.99999, 1.0);
+    
+    mvp_vertex = vec4(in_Position.xy, 1.0, 1.0);
+    vec4 world_vertex = mtx_camera * (mtx_inv_proj * mvp_vertex);
+    world_vertex /= world_vertex.w;
+    last_mvp_vertex = mtx_last_vp * world_vertex;    
+}
+
+#elif FRAGMENT_SHADER
+
+#include "gshader_frag.glsl"
+
+void main(void) {
+    GBufferAttributes attribs = gba_empty();
+    attribs.sky = 1.0;
+    attribs.velocity = gba_default_velocity();
+    
+    write_gbuffer_depth_vel_fx(attribs);
+        
+}
+
+#endif // FRAGMENT_SHADER
  No newline at end of file

          
A => liminal_legacy/liminal/assets/shaders/lib/blur.glsl +142 -0
@@ 0,0 1,142 @@ 
+// various blur tool functions
+// you must declare sample_blur_texture before including
+// format:
+//      BLUR_TYPE sample_blur_texture(vec2 uv, vec2 offset)
+
+#ifndef BLUR_TYPE
+#define BLUR_TYPE vec4
+#endif
+
+#ifndef BLUR_TEXTURE
+#define BLUR_TEXTURE sample_blur_texture
+#endif
+
+// 4 bilinear lookups
+BLUR_TYPE blur_texture_fastgauss_3x3(vec2 uv, vec2 texel, vec2 size) 
+{
+    vec2 uvp = uv  * size;    
+    vec2 base_uv = floor(uvp + 0.5);    
+    float s = uvp.x + 0.5 - base_uv.x;
+    float t = uvp.y + 0.5 - base_uv.y;    
+    base_uv -= vec2(0.5);
+    base_uv *= texel;
+
+    float uw0 = (3.0 - 2.0 * s);
+    float uw1 = (1.0 + 2.0 * s);
+
+    float u0 = (2.0 - s) / uw0 - 1.0;
+    float u1 = s / uw1 + 1.0;
+
+    float vw0 = (3.0 - 2.0 * t);
+    float vw1 = (1.0 + 2.0 * t);
+
+    float v0 = (2.0 - t) / vw0 - 1.0;
+    float v1 = t / vw1 + 1.0;
+
+    BLUR_TYPE 
+    sum  = uw0 * vw0 * BLUR_TEXTURE(base_uv, vec2(u0, v0) * texel);
+    sum += uw1 * vw0 * BLUR_TEXTURE(base_uv, vec2(u1, v0) * texel);
+    sum += uw0 * vw1 * BLUR_TEXTURE(base_uv, vec2(u0, v1) * texel);
+    sum += uw1 * vw1 * BLUR_TEXTURE(base_uv, vec2(u1, v1) * texel);
+
+    return sum / 16.0;
+}
+
+// 9 bilinear lookups
+BLUR_TYPE blur_texture_fastgauss_5x5(vec2 uv, vec2 texel, vec2 size) 
+{
+    vec2 uvp = uv  * size;    
+    vec2 base_uv = floor(uvp + 0.5);    
+    float s = uvp.x + 0.5 - base_uv.x;
+    float t = uvp.y + 0.5 - base_uv.y;    
+    base_uv -= vec2(0.5);
+    base_uv *= texel;
+    
+    float uw0 = (4.0 - 3.0 * s);
+    float uw1 = 7.0;
+    float uw2 = (1.0 + 3.0 * s);
+
+    float u0 = (3.0 - 2.0 * s) / uw0 - 2.0;
+    float u1 = (3.0 + s) / uw1;
+    float u2 = s / uw2 + 2.0;
+
+    float vw0 = (4.0 - 3.0 * t);
+    float vw1 = 7.0;
+    float vw2 = (1.0 + 3.0 * t);
+
+    float v0 = (3.0 - 2.0 * t) / vw0 - 2.0;
+    float v1 = (3.0 + t) / vw1;
+    float v2 = t / vw2 + 2.0;
+    
+    BLUR_TYPE
+    sum  = uw0 * vw0 * BLUR_TEXTURE(base_uv, vec2(u0, v0) * texel);
+    sum += uw1 * vw0 * BLUR_TEXTURE(base_uv, vec2(u1, v0) * texel);
+    sum += uw2 * vw0 * BLUR_TEXTURE(base_uv, vec2(u2, v0) * texel);
+
+    sum += uw0 * vw1 * BLUR_TEXTURE(base_uv, vec2(u0, v1) * texel);
+    sum += uw1 * vw1 * BLUR_TEXTURE(base_uv, vec2(u1, v1) * texel);
+    sum += uw2 * vw1 * BLUR_TEXTURE(base_uv, vec2(u2, v1) * texel);
+
+    sum += uw0 * vw2 * BLUR_TEXTURE(base_uv, vec2(u0, v2) * texel);
+    sum += uw1 * vw2 * BLUR_TEXTURE(base_uv, vec2(u1, v2) * texel);
+    sum += uw2 * vw2 * BLUR_TEXTURE(base_uv, vec2(u2, v2) * texel);
+
+    return sum / 144.0;
+}
+
+// 16 bilinear lookups
+BLUR_TYPE blur_texture_fastgauss_7x7(vec2 uv, vec2 texel, vec2 size) 
+{
+    vec2 uvp = uv  * size;    
+    vec2 base_uv = floor(uvp + 0.5);    
+    float s = uvp.x + 0.5 - base_uv.x;
+    float t = uvp.y + 0.5 - base_uv.y;    
+    base_uv -= vec2(0.5);
+    base_uv *= texel;
+    
+    float uw0 = (5.0 * s - 6.0);
+    float uw1 = (11.0 * s - 28.0);
+    float uw2 = -(11.0 * s + 17.0);
+    float uw3 = -(5.0 * s + 1.0);
+
+    float u0 = (4.0 * s - 5.0) / uw0 - 3.0;
+    float u1 = (4.0 * s - 16.0) / uw1 - 1.0;
+    float u2 = -(7.0 * s + 5.0) / uw2 + 1.0;
+    float u3 = -s / uw3 + 3.0;
+
+    float vw0 = (5.0 * t - 6.0);
+    float vw1 = (11.0 * t - 28.0);
+    float vw2 = -(11.0 * t + 17.0);
+    float vw3 = -(5.0 * t + 1.0);
+
+    float v0 = (4.0 * t - 5.0) / vw0 - 3.0;
+    float v1 = (4.0 * t - 16.0) / vw1 - 1.0;
+    float v2 = -(7.0 * t + 5.0) / vw2 + 1.0;
+    float v3 = -t / vw3 + 3.0;
+    
+    BLUR_TYPE
+    sum  = uw0 * vw0 * BLUR_TEXTURE(base_uv, vec2(u0, v0) * texel);
+    sum += uw1 * vw0 * BLUR_TEXTURE(base_uv, vec2(u1, v0) * texel);
+    sum += uw2 * vw0 * BLUR_TEXTURE(base_uv, vec2(u2, v0) * texel);
+    sum += uw3 * vw0 * BLUR_TEXTURE(base_uv, vec2(u3, v0) * texel);
+
+    sum += uw0 * vw1 * BLUR_TEXTURE(base_uv, vec2(u0, v1) * texel);
+    sum += uw1 * vw1 * BLUR_TEXTURE(base_uv, vec2(u1, v1) * texel);
+    sum += uw2 * vw1 * BLUR_TEXTURE(base_uv, vec2(u2, v1) * texel);
+    sum += uw3 * vw1 * BLUR_TEXTURE(base_uv, vec2(u3, v1) * texel);
+
+    sum += uw0 * vw2 * BLUR_TEXTURE(base_uv, vec2(u0, v2) * texel);
+    sum += uw1 * vw2 * BLUR_TEXTURE(base_uv, vec2(u1, v2) * texel);
+    sum += uw2 * vw2 * BLUR_TEXTURE(base_uv, vec2(u2, v2) * texel);
+    sum += uw3 * vw2 * BLUR_TEXTURE(base_uv, vec2(u3, v2) * texel);
+
+    sum += uw0 * vw3 * BLUR_TEXTURE(base_uv, vec2(u0, v3) * texel);
+    sum += uw1 * vw3 * BLUR_TEXTURE(base_uv, vec2(u1, v3) * texel);
+    sum += uw2 * vw3 * BLUR_TEXTURE(base_uv, vec2(u2, v3) * texel);
+    sum += uw3 * vw3 * BLUR_TEXTURE(base_uv, vec2(u3, v3) * texel);
+
+    return sum / 2704.0;
+}
+
+#undef BLUR_TYPE
+#undef BLUR_TEXTURE

          
A => liminal_legacy/liminal/assets/shaders/lib/brdf.glsl +130 -0
@@ 0,0 1,130 @@ 
+#ifndef M_PI
+#define M_PI 3.141592653589793
+#endif
+
+// implementation of BRDF model after 
+// http://disney-animation.s3.amazonaws.com/library/s2012_pbs_disney_brdf_notes_v2.pdf
+// and
+// http://blog.selfshadow.com/publications/s2013-shading-course/karis/s2013_pbs_epic_notes_v2.pdf
+// some more background on this blog:
+// http://graphicrants.blogspot.de/2013/08/specular-brdf-reference.html
+
+// reference:
+// cos(Ol) = dot(n,l)
+// cos(Ov) = dot(n,v)
+// cos(Oh) = dot(n,h)
+// cos(Od) = dot(l,h) [or: dot(v,h)]
+//
+// f(l,v) = diffuse + (D(Oh)*F(Od)*G(Ol,Ov) / (4.0*cos(Ol)*cos(Ov))
+// 
+// D = microfacet distribution function, specular peak shape
+// F = fresnel reflection coefficient
+// G = geometric attenuation / shadowing factor
+
+#define BRDF_F0_SILICON 0.3561
+#define BRDF_F0_DIAMOND 0.17225
+#define BRDF_F0_GLASS 0.04
+#define BRDF_F0_WATER 0.0204
+#define BRDF_F0_ICE 0.017
+#define BRDF_F0_VACUUM = 0.0
+
+struct BRDFAttributes {
+    float metallic; // 0..1
+    float roughness; // 0..1
+    vec3 base_color; //  
+    float cos_Ol; // dot(n,l)
+    vec3 l; // light vector (points towards light)
+    vec3 v; // eye/camera vector (points towards camera)
+    vec3 n; // unit normal (points away from surface)
+} brdfa;
+
+// generated by brdf_build_vars()
+struct BRDFVars {
+    float F0; // specular reflectance at n=1 (0.04 for glass)
+    vec3 h; // unit halfway vector (l+v)/(|l+v|)
+    float cos_Ov; // dot(n,v)
+    float cos_Oh; // dot(n,h)
+    float cos_Od; // dot(l,h) == dot(v,h)
+} brdfv;
+
+void brdf_build_vars() {
+    vec3 h = normalize(brdfa.l + brdfa.v);
+    
+    brdfv = BRDFVars(
+        mix(BRDF_F0_GLASS, 0.9, brdfa.metallic),
+        h,
+        max(0.0, dot(brdfa.n, brdfa.v)),
+        max(0.0, dot(brdfa.n, h)),
+        max(0.0, dot(brdfa.v, h))
+    );
+}
+
+float brdf_lambert() {
+    return 1.0 / M_PI;
+}
+
+#define BRDF_SPEC_BIAS 0.00015
+#define BRDF_MAX_SPEC 128.0
+
+float brdf_specular_D_GCX() {
+    float a = brdfa.roughness*brdfa.roughness;
+    float a2 = a*a;
+    
+    float Dd = max(BRDF_SPEC_BIAS, brdfv.cos_Oh*brdfv.cos_Oh*(a2 - 1.0)+1.0);
+    
+    return a2/(M_PI*Dd*Dd);
+}
+
+float brdf_specular_Gd_Schlick() {
+    float r = (brdfa.roughness+1.0); 
+    float k = (r*r) / 8.0;
+    float ik = 1.0 - k;
+    
+    float Gdlvh = 4.0 * (brdfv.cos_Ov*ik + k) * (brdfa.cos_Ol*ik + k);
+    
+    return (1.0 / Gdlvh);
+}
+
+float brdf_specular_F_Schlick() {
+    float p = (-5.55473*brdfv.cos_Od-6.98316)*brdfv.cos_Od;
+    return brdfv.F0 + (1.0 - brdfv.F0)*pow(2.0, p);
+}
+
+float brdf_specular() {
+    float Dh = brdf_specular_D_GCX();
+    float Fvh = brdf_specular_F_Schlick();
+    float Gdlvh = brdf_specular_Gd_Schlick();
+    
+    return clamp((Dh*Fvh*Gdlvh), 0.0, BRDF_MAX_SPEC);
+}
+
+vec3 brdf_mix_colors(vec3 fd, vec3 fs, vec3 color, float m) {
+    return mix(fd, fs, m) * color + fs * (1.0 - m);
+}
+
+vec3 brdf_mix(float fd, float fs, vec3 color, float m) {
+    return mix(fd, fs, m) * color + (fs * (1.0 - m));
+}
+
+vec3 brdf_evaluate_fluorescent() {
+    brdf_build_vars();
+
+    float fs; // specular    
+    fs = (brdfa.roughness > 0.0)?brdf_specular():0.0;    
+    return fs * mix(vec3(1.0), brdfa.base_color, brdfa.metallic); 
+}
+
+
+vec3 brdf_evaluate() {
+    brdf_build_vars();
+
+    float fd; // diffuse
+    float fs; // specular
+    
+    fd = brdf_lambert();
+    fs = (brdfa.roughness > 0.0)?brdf_specular():0.0;
+    
+    return brdf_mix(fd, fs, brdfa.base_color, brdfa.metallic);
+}
+
+

          
A => liminal_legacy/liminal/assets/shaders/lib/evsm.glsl +86 -0
@@ 0,0 1,86 @@ 
+
+
+#define EVSM_ENABLE 1
+
+#ifndef USE_EVSM4
+#define USE_EVSM4 0
+#endif
+
+#define EVSM4 USE_EVSM4
+
+// positive and negative exponents
+const vec2 exponents = vec2(10.0, 24.0);
+const float light_bleeding_bias = 0.2;
+#if EVSM_ENABLE
+const float min_variance = 1e-3;
+#else // !EVSM_ENABLE
+const float min_variance = 1e-5;
+#endif // !EVSM_ENABLE
+
+// Applies exponential warp to shadow map depth, input depth should be in [0, 1]
+vec2 warp_depth(float depth, vec2 exponents) {
+    // Rescale depth into [-1, 1]
+    depth = 2.0 * depth - 1.0;
+    float pos =  exp( exponents.x * depth);
+    float neg = -exp(-exponents.y * depth);
+    return vec2(pos, neg);
+}
+
+float warp_depth1(float depth, vec2 exponents) {
+    // Rescale depth into [-1, 1]
+    depth = 2.0 * depth - 1.0;
+    return exp( exponents.x * depth);
+}
+
+float linstep(float a, float b, float v) {
+    return clamp((v - a) / (b - a), 0.0, 1.0);
+}
+
+// Reduces VSM light bleedning
+float reduce_light_bleeding(float p_max, float amount) {
+  // Remove the [0, amount] tail and linearly rescale (amount, 1].
+   return linstep(amount, 1.0, p_max);
+}
+
+float chebyshev_upper_bound(vec2 moments, float mean, float minVariance,
+                            float lightBleedingReduction) {
+    // Compute variance
+    float variance = moments.y - (moments.x * moments.x);
+    variance = max(variance, minVariance);
+
+    // Compute probabilistic upper bound
+    float d = mean - moments.x;
+    float pMax = variance / (variance + (d * d));
+
+    pMax = reduce_light_bleeding(pMax, lightBleedingReduction);
+
+    // One-tailed Chebyshev
+    return (mean <= moments.x ? 1.0 : pMax);
+}
+
+/*
+vec4 convert_depth_vsm(float depth) {
+    float moment1 = depth;
+    float moment2 = depth * depth;
+
+    return vec4( moment1, moment2, 0.0, 0.0);    
+}
+*/
+
+vec4 convert_depth_evsm(float depth) {
+    vec2 moment = warp_depth(depth, exponents);
+    
+    vec4 moment4 = vec4(moment.xy, moment.xy*moment.xy);
+
+#if EVSM4    
+    return moment4;
+#else
+    return moment4.xzxz;
+#endif    
+}
+
+#if EVSM_ENABLE
+#define convert_depth convert_depth_evsm
+#else
+#define convert_depth convert_depth_vsm
+#endif

          
A => liminal_legacy/liminal/assets/shaders/lib/fog.glsl +78 -0
@@ 0,0 1,78 @@ 
+// implementation for fog / fog sphere
+
+#ifndef USE_FOG_SPHERE
+#define USE_FOG_SPHERE 0
+#endif
+
+#ifndef USE_FOG_RANGE
+#define USE_FOG_RANGE 0
+#endif
+
+#ifndef USE_FOG_TEXTURE
+#define USE_FOG_TEXTURE 0
+#endif
+
+// distance in units until fog reaches half intensity
+const float fog_radius = 0.0;
+#if USE_FOG_RANGE
+uniform float fog_range = 100.0;
+#else
+#define fog_range far
+#endif
+uniform float fog_power = 0.2;
+uniform float fog_thickness = 1.0;
+
+#if USE_FOG_TEXTURE
+uniform sampler2D fog_map;
+uniform vec4 fog_uv_scale_offset = vec4(1.0,1.0,0.0,0.0);
+#else
+uniform vec3 fog_color = vec3(0.3, 0.7, 1.0);
+#endif
+
+#if USE_FOG_SPHERE
+
+uniform float fog_falloff_radius = 20.0;
+
+vec3 closest_ray_point(in vec3 pos, in vec3 view_pos, in vec3 view_dir, in float surface_distance) {
+    float t0 = dot(view_dir, pos - view_pos) / dot(view_dir, view_dir);
+    
+    return view_pos + view_dir * min(surface_distance, max(0.0, t0));
+}
+
+float get_fog_intensity(in vec3 center, in vec3 view_pos, in vec3 view_dir, in float surface_distance) {
+    vec3 fog_point = closest_ray_point(center, view_pos, view_dir, surface_distance);
+    
+    float rc = length(fog_point);        
+    float h = max((rc - fog_radius) / fog_falloff_radius, 0.0);
+    float height_intensity = exp(-h*h);
+
+    float d = surface_distance / fog_range;
+    float depth_intensity = 1.0 - exp(-d*d); 
+    
+    return height_intensity * depth_intensity; 
+}
+
+# else
+
+float get_fog_intensity(in vec3 center, in vec3 view_pos, in vec3 view_dir, in float surface_distance) {
+    float d = surface_distance / fog_range;
+    float depth_intensity = max(1.0 - exp(-d*d),
+        // full fadeout towards end 
+        clamp(3.0*(d-1.0)+1.0,0.0,1.0));
+    
+    return depth_intensity; 
+}
+
+#endif
+
+vec3 mix_fog(vec3 color, float fog_intensity, vec3 envdir) { 
+    color = color * (1.0 - fog_intensity * fog_thickness);
+    
+#if USE_FOG_TEXTURE
+    vec2 uv = vec2(envdir.z*0.5+0.5, fog_intensity);
+    vec3 fog_color = textureLod(fog_map, 
+    uv*fog_uv_scale_offset.xy + fog_uv_scale_offset.zw, 0.0).rgb;
+#endif
+    
+    return color + fog_color * (fog_power * fog_intensity);
+}    

          
A => liminal_legacy/liminal/assets/shaders/lib/hammersley.glsl +31 -0
@@ 0,0 1,31 @@ 
+#ifndef M_PI
+#define M_PI 3.141592653589793
+#endif
+
+float hammersley_radical_inverse_VdC(uint bits) {
+     bits = (bits << 16u) | (bits >> 16u);
+     bits = ((bits & 0x55555555u) << 1u) | ((bits & 0xAAAAAAAAu) >> 1u);
+     bits = ((bits & 0x33333333u) << 2u) | ((bits & 0xCCCCCCCCu) >> 2u);
+     bits = ((bits & 0x0F0F0F0Fu) << 4u) | ((bits & 0xF0F0F0F0u) >> 4u);
+     bits = ((bits & 0x00FF00FFu) << 8u) | ((bits & 0xFF00FF00u) >> 8u);
+     return float(bits) * 2.3283064365386963e-10; // / 0x100000000
+}
+
+vec2 hammersley2d(uint i, uint N) {
+     return vec2(float(i)/float(N), hammersley_radical_inverse_VdC(i));
+}
+
+ vec3 hemisphere_sample_uniform(float u, float v) {
+     float phi = v * 2.0 * M_PI;
+     float cosTheta = 1.0 - u;
+     float sinTheta = sqrt(1.0 - cosTheta * cosTheta);
+     return vec3(cos(phi) * sinTheta, sin(phi) * sinTheta, cosTheta);
+ }
+    
+ vec3 hemisphere_sample_cos(float u, float v) {
+     float phi = v * 2.0 * M_PI;
+     float cosTheta = sqrt(1.0 - u);
+     float sinTheta = sqrt(1.0 - cosTheta * cosTheta);
+     return vec3(cos(phi) * sinTheta, sin(phi) * sinTheta, cosTheta);
+ }
+ 
  No newline at end of file

          
A => liminal_legacy/liminal/assets/shaders/lib/hslhsv.glsl +29 -0
@@ 0,0 1,29 @@ 
+vec3 hue2rgb(float hue) {
+    return clamp( 
+        abs(mod(hue * 6.0 + vec3(0.0, 4.0, 2.0), 6.0) - 3.0) - 1.0, 
+        0.0, 1.0);
+}
+
+vec3 hsl2rgb(vec3 c) {
+    vec3 rgb = hue2rgb(c.x);
+    return c.z + c.y * (rgb - 0.5) * (1.0 - abs(2.0 * c.z - 1.0));
+}
+
+vec3 hsv2rgb(vec3 c) {
+    vec3 rgb = hue2rgb(c.x);
+    return c.z * mix(vec3(1.0), rgb, c.y);
+}
+
+vec3 hsv2rgbc(vec3 c) {
+    vec3 rgb = hue2rgb(c.x);
+    // rgb = smoothstep(vec3(0.0),vec3(1.0),rgb);
+    rgb = rgb * rgb * (3.0 - 2.0*rgb);
+    return c.z * mix(vec3(1.0), rgb, c.y);
+}
+
+// ratio: 3 = neon, 4 = refracted, 5+ = approximate white
+vec3 physhue2rgb(float hue, float ratio) {
+    return smoothstep(
+        vec3(0.0),vec3(1.0),
+        abs(mod(hue + vec3(0.0,1.0,2.0)*(1.0/ratio),1.0)*2.0-1.0));
+}

          
A => liminal_legacy/liminal/assets/shaders/lib/ibl_sample.glsl +146 -0
@@ 0,0 1,146 @@ 
+
+#define IBL_MAX_SPEC 128.0
+#define IBL_F0_GLASS 0.04
+#define IBL_NUM_SAMPLES 1u
+
+// image-based lighting
+////////////////////////////////////////////////////////////////////////////////
+
+struct IBLAttributes {
+    float metallic; // 0..1
+    float roughness; // 0..1
+    vec3 base_color; //  
+    vec3 v; // eye/camera vector (points towards camera)
+    vec3 n; // unit normal (points away from surface)
+} ibla;
+
+// generated by brdf_build_vars()
+struct IBLVars {
+    float F0; // specular reflectance at n=1 (0.04 for glass)
+    vec3 l; // light vector (generated)
+    vec3 h; // unit halfway vector (l+v)/(|l+v|)
+    float cos_Ov; // dot(n,v)
+    float cos_Ol; // dot(n,l)
+    float cos_Oh; // dot(n,h)
+    float cos_Od; // dot(l,h) == dot(v,h)
+} iblv;
+
+void ibl_sample_build_vars() {
+    iblv.F0 = mix(IBL_F0_GLASS, 0.9, ibla.metallic);
+    iblv.cos_Ov = max(0.0, dot(ibla.n, ibla.v));
+}
+
+void ibl_sample_specular_update_vars() {
+    // iblv.h must be set from outside
+    float cos_Od = dot(ibla.v, iblv.h);
+    iblv.l = 2.0 * cos_Od*iblv.h - ibla.v;
+    
+    iblv.cos_Ol = max(0.0, dot(ibla.n, iblv.l));
+    iblv.cos_Oh = max(0.0, dot(ibla.n, iblv.h));
+    iblv.cos_Od = max(0.0, cos_Od);
+}
+
+void ibl_sample_diffuse_update_vars() {
+    // iblv.h must be set from outside
+    float cos_Od = dot(ibla.v, iblv.h);
+    iblv.l = iblv.h;
+    
+    iblv.cos_Ol = max(0.0, dot(ibla.n, iblv.l));
+    iblv.cos_Oh = max(0.0, dot(ibla.n, iblv.h));
+    iblv.cos_Od = max(0.0, cos_Od);
+}
+
+vec3 ibl_specular_importance_sample_GCX( vec2 Xi)
+{
+    float a = ibla.roughness * ibla.roughness;
+    float Phi = 2.0 * M_PI * Xi.x;
+    float CosTheta = sqrt( (1.0 - Xi.y) / ( 1.0 + (a*a - 1.0) * Xi.y ) );
+    float SinTheta = sqrt( 1.0 - CosTheta * CosTheta );
+    vec3 H = vec3(
+        SinTheta * cos( Phi ),
+        SinTheta * sin( Phi ),
+        CosTheta);
+    vec3 UpVector = abs(ibla.n.z) < 0.999 ? vec3(0.0,0.0,1.0) : vec3(1.0,0.0,0.0);
+    vec3 TangentX = normalize( cross( UpVector, ibla.n ) );
+    vec3 TangentY = cross( ibla.n, TangentX );
+    // Tangent to world space
+    return TangentX * H.x + TangentY * H.y + ibla.n * H.z;
+}
+
+vec3 ibl_diffuse_importance_sample_GCX( vec2 Xi)
+{
+    float Phi = 2.0 * M_PI * Xi.x;
+    float CosTheta = sqrt( 1.0 - Xi.y );
+    float SinTheta = sqrt( 1.0 - CosTheta * CosTheta );
+    vec3 H = vec3(
+        SinTheta * cos( Phi ),
+        SinTheta * sin( Phi ),
+        CosTheta);
+    vec3 UpVector = abs(ibla.n.z) < 0.999 ? vec3(0.0,0.0,1.0) : vec3(1.0,0.0,0.0);
+    vec3 TangentX = normalize( cross( UpVector, ibla.n ) );
+    vec3 TangentY = cross( ibla.n, TangentX );
+    // Tangent to world space
+    return TangentX * H.x + TangentY * H.y + ibla.n * H.z;
+}
+
+vec3 ibl_cubemap_vector(in vec3 v) {
+    return vec3(v.xz, -v.y);
+}
+
+vec3 ibl_sample_cubemap(in samplerCube sampler, in vec3 l, in float lod) {
+    return textureLod(sampler, ibl_cubemap_vector(l), lod).rgb;
+}
+
+#include "hammersley.glsl"
+
+float ibl_specular_G_Smith() {
+    float r = ibla.roughness; 
+    float k = (r*r) / 2.0;
+    float ik = 1.0 - k;
+    
+    float Gnlvh = iblv.cos_Ol;
+    float Gdlvh = (iblv.cos_Ov*ik + k) * (iblv.cos_Ol*ik + k);
+    
+    return (Gnlvh / Gdlvh);
+}
+
+vec3 ibl_sample_specular_diffuse(in samplerCube sampler, out vec3 diffuse)
+{
+    ibl_sample_build_vars();
+
+    float miplevel = ibla.roughness * 16.0;
+    vec3 specular_color = mix(vec3(1.0), ibla.base_color, ibla.metallic);
+    vec3 diffuse_color = ibla.base_color * (1.0 - ibla.metallic);
+
+    diffuse = vec3(0.0);
+    vec3 SpecularLighting = vec3(0.0);
+    const uint NumSamples = IBL_NUM_SAMPLES;
+    for( uint i = 0u; i < NumSamples; i++ )
+    {
+        vec2 Xi = hammersley2d( i, NumSamples );
+        iblv.h = ibl_specular_importance_sample_GCX(Xi);
+        ibl_sample_specular_update_vars();
+        if ( iblv.cos_Ol > 0.0 )
+        {
+            vec3 SampleColor = ibl_sample_cubemap(sampler, iblv.l, miplevel ).rgb;
+            float G = ibl_specular_G_Smith();
+            float Fc = pow(1.0 - iblv.cos_Od, 5.0 );
+            vec3 F = (1.0 - Fc) * specular_color + vec3(Fc);
+            // Incident light = SampleColor * NoL
+            // Microfacet specular = D*G*F / (4*NoL*NoV)
+            // pdf = D * NoH / (4 * VoH)
+            float fs = G * iblv.cos_Od / (iblv.cos_Oh);
+            SpecularLighting += SampleColor * F * fs;            
+        }
+        iblv.h = ibl_diffuse_importance_sample_GCX(Xi);
+        ibl_sample_diffuse_update_vars();
+        if ( iblv.cos_Ol > 0.0 )
+        {
+            vec3 SampleColor = ibl_sample_cubemap(sampler, iblv.l, 16.0 ).rgb;
+            diffuse += SampleColor;
+        }
+    }
+    diffuse = ibla.base_color * clamp(diffuse / float(NumSamples), 0.0, IBL_MAX_SPEC);
+    return clamp(SpecularLighting / float(NumSamples), 0.0, IBL_MAX_SPEC);
+}
+

          
A => liminal_legacy/liminal/assets/shaders/lib/mapping.glsl +281 -0
@@ 0,0 1,281 @@ 
+
+//
+// as described in Realtime Texture Quilting
+// by Hugh Malan, Jeff Cairns
+// http://hl.udogs.net/files/Uploads/%20User%20Uploads/PunkUser's%20Uploads/Malan%20-%20Real-time%20Texture%20Quilting%20(Siggraph%202011%20Advances%20in%20Real-Time%20Rendering%20Course).pdf
+#ifndef USE_QUILTING
+#define USE_QUILTING 0
+#endif
+
+#ifndef USE_TEXTURE_ARRAY
+#define USE_TEXTURE_ARRAY 0
+#endif
+
+// combined luma/roughness/metallic/alpha/normal uv/emissive/quilt factor
+// read from two textures
+#ifndef USE_LRMANNEQ
+#define USE_LRMANNEQ 0
+#endif
+
+#if USE_TEXTURE_ARRAY
+#define SAMPLER2DTYPE sampler2DArray
+#define VECUVTYPE vec3
+#define IN_TEXCOORD in_TexCoord1
+#else
+#define SAMPLER2DTYPE sampler2D
+#define VECUVTYPE vec2
+#define IN_TEXCOORD in_TexCoord0
+#endif
+
+#if USE_QUILTING
+
+struct QuiltVertex {
+    vec3 tangent;
+    vec3 bitangent;
+    vec3 origin;
+    vec4 color;
+    VECUVTYPE texcoord;
+};
+
+varying QuiltVertex quilt_blended_vertex[3];
+varying vec3 quilt_position;
+varying vec3 quilt_weight;
+varying vec3 quilt_normal;
+varying float quilt_scale;
+
+#else // !USE_QUILTING
+
+varying VECUVTYPE texcoord;
+
+#endif // USE_QUILTING
+
+
+#if VERTEX_SHADER
+
+vec3 mapping_orthogonal(vec3 v)
+{
+    return abs(v.x) > abs(v.z) ? vec3(-v.y, v.x, 0.0)
+                               : vec3(0.0, -v.z, v.y);
+}
+
+void write_texcoord() {
+#if USE_QUILTING
+
+    int index = int(in_Position.w + 0.5);
+    quilt_position = in_Position.xyz;
+    
+    quilt_scale = length(in_Tangent);
+    
+    quilt_normal = normalize(in_Normal); //normalize(in_Normal);
+    vec3 tangent = normalize(
+        normalize(in_Tangent)
+         - dot(in_Tangent, quilt_normal)*quilt_normal);
+    vec3 bitangent = cross(quilt_normal, tangent);
+    
+    for (int i = 0; i < 3; ++i) {
+        if (i == index) {
+            quilt_blended_vertex[i].tangent = tangent;
+            quilt_blended_vertex[i].bitangent = bitangent;
+            quilt_blended_vertex[i].origin = in_Position.xyz;
+            quilt_blended_vertex[i].color = in_Color;
+            quilt_blended_vertex[i].texcoord = IN_TEXCOORD;
+            quilt_weight[i] = 1.0;
+        } else {
+            quilt_blended_vertex[i].tangent = vec3(0.0);
+            quilt_blended_vertex[i].bitangent = vec3(0.0);
+            quilt_blended_vertex[i].origin = vec3(0.0);
+            quilt_blended_vertex[i].color = vec4(0.0);
+            quilt_blended_vertex[i].texcoord = VECUVTYPE(0.0);
+            quilt_weight[i] = 0.0;
+        }        
+    }
+
+#else // !USE_QUILTING
+
+    texcoord = IN_TEXCOORD;
+
+#endif // USE_QUILTING
+}
+
+#elif FRAGMENT_SHADER
+
+#if USE_QUILTING
+vec3 quilt_constant = 1.0 / quilt_weight;
+
+QuiltVertex quilt_get_vertex(int k) {
+    QuiltVertex qo = quilt_blended_vertex[k];
+    float w = quilt_constant[k];
+    return QuiltVertex(
+        qo.tangent * w,
+        qo.bitangent * w,
+        qo.origin * w,
+        qo.color * w,
+        qo.texcoord * w
+    );
+}
+
+QuiltVertex quilt_vertex[3] = QuiltVertex[](
+    quilt_get_vertex(0),
+    quilt_get_vertex(1),
+    quilt_get_vertex(2)
+);
+
+VECUVTYPE calc_quilt_uv(in QuiltVertex qv) {
+    vec3 d = quilt_position - qv.origin;
+    
+    VECUVTYPE uv = qv.texcoord;
+    uv.xy += vec2(dot(d, qv.tangent), -dot(d, qv.bitangent))*quilt_scale + 0.5;
+        
+    return uv;
+}
+
+VECUVTYPE quilt_uv[3] = VECUVTYPE[](
+    calc_quilt_uv(quilt_vertex[0]),
+    calc_quilt_uv(quilt_vertex[1]),
+    calc_quilt_uv(quilt_vertex[2])
+); 
+
+uniform float quilt_transition_range = 0.5;
+vec3 calc_quilt_weights(vec3 fitness) {
+    float maxfit = max(fitness.x, max(fitness.y, fitness.z));
+    vec3 raw_weight = max(vec3(0.0), 
+        fitness+quilt_transition_range-maxfit)*fitness;
+    return raw_weight / max(0.0001,dot(raw_weight, vec3(1.0)));
+}
+
+vec3 get_quilt_normal(in vec3 vertex_normal) {
+    vec3 a = quilt_vertex[1].origin - quilt_vertex[0].origin;
+    vec3 b = quilt_vertex[2].origin - quilt_vertex[0].origin;
+    vec3 n = mat3(mtx_model) * cross(a,b);
+    // reproject on vertex normal to reconstruct sign
+    return mix(vertex_normal, 
+        normalize(n * dot(n, vertex_normal)), 0.5);
+}
+
+void quilt_restore_vertices() {
+    quilt_vertex[0] = quilt_get_vertex(0);
+    quilt_vertex[1] = quilt_get_vertex(1);
+    quilt_vertex[2] = quilt_get_vertex(2);
+}
+
+vec3 unpack_normal(vec2 nxy) {
+    nxy = nxy*2.0-1.0;
+    return vec3(nxy, sqrt(1.0-nxy.x*nxy.x-nxy.y*nxy.y));
+}
+
+mat3 quilt_normal_matrix(in QuiltVertex qv, in vec3 flat_normal, in float sm) {
+    
+    vec3 n = normalize(
+        flat_normal + cross(qv.tangent, qv.bitangent)*sm);
+
+    return mat3(qv.tangent, qv.bitangent, n); 
+}
+
+float df_weight(float x) {
+    return clamp((1.0-x)*2.0-1.0, 0.0, 1.0);
+}
+
+// mat0: (Nx, Ny), Depth, OBJID
+// mat1: AO, Emissive, Roughness, Metallic
+void map_texture_dm(in SAMPLER2DTYPE sampler, 
+    out vec2 mat0, out vec4 mat1, out vec3 n, out vec4 color) {
+#if USE_TEXTURE_ARRAY
+    vec4 a0 = texture(sampler, quilt_uv[0]);
+    vec4 a1 = texture(sampler, quilt_uv[1]);
+    vec4 a2 = texture(sampler, quilt_uv[2]);
+
+    vec4 b0 = texture(sampler, quilt_uv[0] + vec3(0.0,0.0,1.0));
+    vec4 b1 = texture(sampler, quilt_uv[1] + vec3(0.0,0.0,1.0));
+    vec4 b2 = texture(sampler, quilt_uv[2] + vec3(0.0,0.0,1.0));
+
+    vec3 quilt_normal_weight = calc_quilt_weights(
+    vec3(a0.z, a1.z, a2.z) * quilt_weight);
+   
+    color = clamp(quilt_vertex[0].color*quilt_normal_weight[0]
+      + quilt_vertex[1].color*quilt_normal_weight[1]
+      + quilt_vertex[2].color*quilt_normal_weight[2], 0.0, 1.0);
+
+#if 0      
+    float border = 1.0;
+    
+    if (quilt_vertex[0].color.w > 1.5) {
+        border = min(border, 1.0-quilt_normal_weight[0]);
+    }
+    if (quilt_vertex[1].color.w > 1.5) {
+        border = min(border, 1.0-quilt_normal_weight[1]);
+    }
+    if (quilt_vertex[2].color.w > 1.5) {
+        border = min(border, 1.0-quilt_normal_weight[2]);
+    }
+    
+    if ((quilt_vertex[0].color.w > 1.5) && (quilt_vertex[1].color.w > 1.5)) {
+        border = min(border, quilt_normal_weight[2]); 
+    }
+    if ((quilt_vertex[1].color.w > 1.5) && (quilt_vertex[2].color.w > 1.5)) {
+        border = min(border, quilt_normal_weight[0]); 
+    }
+    if ((quilt_vertex[2].color.w > 1.5) && (quilt_vertex[0].color.w > 1.5)) {
+        border = min(border, quilt_normal_weight[1]); 
+    }
+    
+    min(quilt_normal_weight[0], min(quilt_normal_weight[1],
+        quilt_normal_weight[2])); */
+      
+    float bs = (1.0 - clamp(border*16.0, 0.0, 1.0));
+      
+    color += 0.3*bs;
+#endif
+    
+    mat0 = a0.zw*quilt_normal_weight[0]
+      + a1.zw*quilt_normal_weight[1]
+      + a2.zw*quilt_normal_weight[2]; 
+
+    mat1 = b0*quilt_normal_weight[0]
+      + b1*quilt_normal_weight[1]
+      + b2*quilt_normal_weight[2];
+    
+    float w = 0.0;
+    
+    vec3 flat_normal = quilt_normal; //get_quilt_normal(quilt_normal)*(1.0 - w);
+    
+    vec3 n0 = quilt_normal_matrix(quilt_vertex[0], flat_normal, w)
+             * unpack_normal(a0.xy);
+    vec3 n1 = quilt_normal_matrix(quilt_vertex[1], flat_normal, w)
+             * unpack_normal(a1.xy);
+    vec3 n2 = quilt_normal_matrix(quilt_vertex[2], flat_normal, w)
+             * unpack_normal(a2.xy);
+    
+    n = normalize(n0*quilt_normal_weight[0]
+      + n1*quilt_normal_weight[1]
+      + n2*quilt_normal_weight[2]);
+    n = quilt_normal;
+      
+    //color.w = bs;
+      
+    //color = vec4(quilt_weight, 0.0);
+    
+    //color = vec4(n*0.5+0.5, 0.0);
+      
+#endif
+
+}
+
+vec4 map_texture(in SAMPLER2DTYPE sampler) {
+    vec3 quilt_normal_weight = calc_quilt_weights(quilt_weight);
+    return texture(sampler, 
+        quilt_uv[0]) * quilt_normal_weight[0]
+      + texture(sampler, 
+        quilt_uv[1]) * quilt_normal_weight[1]
+      + texture(sampler, 
+        quilt_uv[2]) * quilt_normal_weight[2];
+}
+
+#else // !USE_QUILTING
+
+vec4 map_texture(in SAMPLER2DTYPE sampler) {
+    return texture(sampler, texcoord);
+}
+
+#endif // USE_QUILTING
+
+#endif // FRAGMENT_SHADER
  No newline at end of file

          
A => liminal_legacy/liminal/assets/shaders/lib/math.glsl +90 -0
@@ 0,0 1,90 @@ 
+#ifndef M_PI
+#define M_PI 3.141592653589793
+#endif
+
+vec3 orthogonal(vec3 v)
+{
+    return abs(v.x) > abs(v.z) ? vec3(-v.y, v.x, 0.0)
+                               : vec3(0.0, -v.z, v.y);
+}
+    
+mat2 mat_rotate_xy(float a) {
+	float ca = cos(a);
+	float sa = sin(a);
+	return mat2(ca,-sa,sa,ca);
+}
+
+float random(vec3 co) {
+    return fract(sin(dot(co.xyz,vec3(12.9898,78.233,91.1743))) * 43758.5453);
+}
+
+float lintri(float x) {
+    return abs(mod(x, 2.0) - 1.0);
+}
+
+float tri(float x) {
+    return abs(mod(x, 2.0) - 1.0)*2.0-1.0;
+}
+
+float linsmoothtri(float x) {
+    return smoothstep(0.0,1.0,abs(mod(x,2.0) - 1.0));
+}
+
+// cos(x) ~= smoothtri(x/PI)
+float smoothtri(float x) {
+    return smoothstep(0.0,1.0,abs(mod(x,2.0) - 1.0))*2.0-1.0;
+}
+
+const mat4 dither_mtx = mat4(
+     vec4( 1.0, 33.0,  9.0, 41.0),
+     vec4(49.0, 17.0, 57.0, 25.0),
+     vec4(13.0, 45.0,  5.0, 37.0),
+     vec4(61.0, 29.0, 53.0, 21.0)
+);
+
+float dither_bayer_pattern(vec2 frag_coord)
+{
+    vec2 puv = mod(frag_coord, 4.0);
+    int x = int(puv.x);
+    int y = int(puv.y);
+    return (dither_mtx[x][y]+1.0)/64.0;
+}
+
+float dither_bayer_pattern_lsb_noise(vec2 frag_coord)
+{
+    vec2 puv = mod(frag_coord, 4.0);
+    int x = int(puv.x);
+    int y = int(puv.y);
+    return (dither_mtx[x][y]+random(vec3(frag_coord,mod(time,1.0))))/64.0;
+}
+
+vec2 mirror_hexagon(vec2 uv) {
+    float ca = sqrt(3.0) / 2.0;
+    mat2 m = mat2(0.5,-ca,ca,0.5);
+    
+    uv.x = abs(uv.x);
+    uv = m * uv;
+    uv.x = abs(uv.x);
+    uv = m * uv;
+    uv.x = abs(uv.x);
+    return uv;
+}
+
+vec2 mirror_dodecagon(vec2 uv) {
+    float ca = sqrt(3.0) / 2.0;
+    mat2 m = mat2(0.5,-ca,ca,0.5);  
+    
+    uv.x = abs(uv.x);
+    uv = m * uv;
+    uv.x = abs(uv.x);
+    uv = m * uv;
+    uv.x = abs(uv.x);
+    
+    m = mat2(ca,-0.5,0.5,ca);
+    uv = m * uv;
+    uv.x = abs(uv.x);
+    
+    return uv;
+}
+
+

          
A => liminal_legacy/liminal/assets/shaders/lib/normal_codec.glsl +57 -0
@@ 0,0 1,57 @@ 
+
+vec2 encode_normal_xy(vec3 n) {
+    return n.xy;
+}
+
+vec3 decode_normal_xy(vec2 enc) {
+    vec3 n;
+    n.xy = enc;
+    n.z = sqrt(1.0-dot(n.xy, n.xy));
+    return n;
+}
+
+vec2 encode_normal_stereographic(vec3 n) {
+    float scale = 1.7777;
+    vec2 enc = n.xy / (n.z+1);
+    enc /= scale;
+    return enc*0.5+0.5;
+}
+
+vec3 decode_normal_stereographic(vec2 enc) {
+    float scale = 1.7777;
+    vec3 nn = vec3(enc.xy,0.0)*vec3(2*scale,2*scale,0.0) +
+        vec3(-scale,-scale,1.0);
+    float g = 2.0 / dot(nn,nn);
+    vec3 n;
+    n.xy = g*nn.xy;
+    n.z = g-1;
+    return n;
+}
+
+// octahedron packing as described in http://jcgt.org/published/0003/02/01/
+vec2 signNotZero(vec2 v) {
+    return vec2((v.x >= 0.0) ? 1.0 : -1.0, (v.y >= 0.0) ? 1.0 : -1.0);
+}
+
+vec2 encode_normal_octahedron(in vec3 v) {
+    vec2 p = v.xy * (1.0 / (abs(v.x) + abs(v.y) + abs(v.z)));
+    return ((v.z <= 0.0) ? ((1.0 - abs(p.yx)) * signNotZero(p)) : p)*0.5+0.5;
+}
+
+vec3 decode_normal_octahedron(vec2 e) {
+    e = e*2.0-1.0;
+    vec3 v = vec3(e.xy, 1.0 - abs(e.x) - abs(e.y));
+    if (v.z < 0) v.xy = (1.0 - abs(v.yx)) * signNotZero(v.xy);
+    return normalize(v);
+}
+
+#if 1
+#define encode_normal encode_normal_octahedron
+#define decode_normal decode_normal_octahedron
+#elif 0
+#define encode_normal encode_normal_stereographic
+#define decode_normal decode_normal_stereographic
+#else
+#define encode_normal encode_normal_xy
+#define decode_normal decode_normal_xy
+#endif
  No newline at end of file

          
A => liminal_legacy/liminal/assets/shaders/lib/perturb.glsl +86 -0
@@ 0,0 1,86 @@ 
+#ifndef PERTURB_GLSL
+#define PERTURB_GLSL
+
+// from "Bump Mapping Unparametrized Surfaces on the GPU"
+// by Morten S. Mikkelsen
+
+// also read 
+// http://www.rorydriscoll.com/2012/01/11/derivative-maps/
+// for additional improvements
+
+// position must be in same space as normal (e.g. modelview)
+
+// from any height source, but pixely artifacts
+vec3 perturb_normal_height(vec3 pos, vec3 normal, float height) {
+    vec3 vSigmaS = dFdx ( pos );
+    vec3 vSigmaT = dFdy ( pos );
+    vec3 vN = normal; // normalized
+    vec3 vR1 = cross ( vSigmaT , vN );
+    vec3 vR2 = cross (vN , vSigmaS );
+    float fDet = dot ( vSigmaS , vR1 );
+    float dBs = dFdx ( height );
+    float dBt = dFdy ( height );
+    vec3 vSurfGrad = sign ( fDet ) * ( dBs * vR1 + dBt * vR2 );
+    return normalize ( abs ( fDet )*vN - vSurfGrad );
+}
+
+// smoother, but only from bump map sources
+vec3 perturb_normal_mapped(vec3 pos, vec3 normal, 
+    in sampler2D height_map, in vec2 uv) {
+    
+    vec3 vSigmaS = dFdx ( pos );
+    vec3 vSigmaT = dFdy ( pos );
+    vec3 vN = normal; // normalized
+    vec3 vR1 = cross ( vSigmaT , vN );
+    vec3 vR2 = cross (vN , vSigmaS );
+    float fDet = dot ( vSigmaS , vR1 );
+    
+    vec2 TexDx = dFdx ( uv );
+    vec2 TexDy = dFdy ( uv );
+    vec2 STll = uv;
+    vec2 STlr = uv + TexDx;
+    vec2 STul = uv + TexDy;
+    float Hll = texture(height_map, STll ).r;
+    float Hlr = texture(height_map, STlr ).r;
+    float Hul = texture(height_map, STul ).r;
+    float dBs = (Hlr - Hll);
+    float dBt = (Hul - Hll);
+    
+    vec3 vSurfGrad = sign ( fDet ) * ( dBs * vR1 + dBt * vR2 );
+    return normalize ( abs ( fDet )*vN - vSurfGrad );
+}
+
+// perturb using derivative height map
+vec3 perturb_normal_derivative_mapped(vec3 pos, vec3 normal, 
+    in sampler2D height_map, in vec2 uv) {
+
+    vec2 dBduv = (texture(height_map, uv ).xy*2.0 - 1.0);
+#if 1    
+    vec2 dim = vec2(textureSize(height_map, 0));
+    dBduv *= dim;
+#endif
+    //dBduv.y *= -1.0; // if t is flipped, this needs be applied
+    
+    vec3 vSigmaS = dFdx ( pos );
+    vec3 vSigmaT = dFdy ( pos );
+    vec3 vN = normal; // normalized
+    vec3 vR1 = cross ( vSigmaT , vN );
+    vec3 vR2 = cross (vN , vSigmaS );
+    float fDet = dot ( vSigmaS , vR1 );
+    
+    vec2 TexDx = dFdx ( uv );
+    vec2 TexDy = dFdy ( uv );
+    float dBs = dot(dBduv, TexDx);
+    float dBt = dot(dBduv, TexDy);
+    
+    vec3 vSurfGrad = sign ( fDet ) * ( dBs * vR1 + dBt * vR2 );
+    return normalize ( abs ( fDet )*vN - vSurfGrad );
+}
+
+// for spatial gradient maps
+vec3 perturb_normal_gradient(vec3 normal, vec3 gradient) {
+    vec3 vSurfGrad = gradient - normal * dot ( normal , gradient );
+    return normalize ( normal - vSurfGrad );
+}
+
+#endif // PERTURB_GLSL
  No newline at end of file

          
A => liminal_legacy/liminal/assets/shaders/lib/sdf_math.glsl +128 -0
@@ 0,0 1,128 @@ 
+#include "lib/snoise3.glsl"
+
+float sdf_sphere(vec3 p, float radius) {
+    return length(p) - radius;
+}
+
+float sdf_plane(vec3 p, vec4 u) {
+    return dot(p,u.xyz)+u.w;
+}
+
+float sdf_torus(vec3 p, float inner_radius, float outer_radius) {
+    vec2 q = vec2(length(p.xy) - outer_radius, p.z);
+    return length(q)-inner_radius;
+}
+
+float sdf_box(vec3 p, vec3 size) {
+    vec3 d = abs(p) - size;
+    return min(max(d.x,max(d.y,d.z)),0.0) + length(max(d,0.0));
+}
+
+float sdf_noise(vec3 p, float alpha, float beta, int octaves, float amp) {
+    float val = 0.0;
+    float sum = 0.0;
+    float scale = 1.0;
+
+    for (int i = 0; i < octaves; ++i) {
+        val = snoise(p)*0.5;
+        sum += val / scale;
+        scale *= alpha;
+        p *= beta;
+    }
+    return sum*amp;
+}
+
+vec2 sdf_union(vec2 a, vec2 b) {
+    return (a.x < b.x)?a:b;
+}
+
+vec2 sdf_intersect(vec2 a, vec2 b) {
+    return (a.x > b.x)?a:b;
+}
+
+vec2 sdf_difference(vec2 a, vec2 b) {
+    return sdf_intersect(a, vec2(-b.x,b.y));    
+}
+
+vec2 sdf_displace(vec2 a, vec2 b) {
+    return vec2(a.x+b.x, a.y);
+}
+
+vec2 sdf_smooth_union(vec2 a, vec2 b, float k) {
+    float h = clamp(0.5+0.5*(b.x-a.x)/k, 0.0, 1.0);
+    return vec2(b.x + (a.x-b.x)*h - k*h*(1.0-h), sdf_union(a,b).y);
+}
+
+float sdf_repeat(float x, float d) {
+    return mod(x+d*0.5,d)-d*0.5;
+}
+
+vec2 sdf_mixuv(float x, vec2 a, vec2 b, float k) {
+    float u = clamp(1.0+(x - 1.0)*k, 0.0, 1.0);
+    return vec2(mix(a.x,b.x,u), mix(a.y,b.y,step(u,0.5)));
+}
+
+/*
+
+vec3 calcNormal(in vec3 p) {
+    vec2 e = vec2(1.0/128.0, 0.0);
+    return normalize(vec3(
+                      map(p + e.xyy) - map(p - e.xyy),
+                      map(p + e.yxy) - map(p - e.yxy),
+                      map(p + e.yyx) - map(p - e.yyx)));
+}
+    
+float calcAO(vec3 p, vec3 n, float radius) {
+    float s = radius/3.0;
+    float ao = 0.0;
+    for (int i = 1; i <= 3; ++i) {
+        float dist = s * float(i);
+    float t = map(p + n*dist);
+        ao += max(0.0, (dist - t) / dist);
+    }
+    return 1.0 - (ao/3.0);
+}
+
+void main( void ) {
+
+    vec2 uv = gl_FragCoord.xy / resolution.xy;
+    vec2 p = 2.0*uv-1.0;
+    p.x *= resolution.x / resolution.y;
+    
+    vec3 r0 = vec3(p.x,p.y,1.0);
+    float bias = 1.5;
+    vec3 rd = vec3(0.0,0.0,-1.0/bias);
+    float tmax = 2.0*bias;
+    float h = 0.0;
+    float t = 0.0;
+    for (int i=0; i<256; i++){
+        h = map(r0 + t*rd);
+        if (h < 0.001 || t > tmax){ 
+            break;
+        }
+        t += h;
+    }
+    
+    vec3 col = vec3(0.0);
+    if (t < tmax) {
+        vec3 pos = r0 + t*rd;
+        vec3 norm = calcNormal(pos);
+        col = (norm*0.5+0.5)*calcAO(pos,norm,0.1);
+    }
+        
+    gl_FragColor = vec4(pow(col,vec3(0.45)),1.0);
+}
+
+float softshadow( in vec3 ro, in vec3 rd, float mint, float k )
+{
+    float res = 1.0;
+    float t = mint;
+    for( int i=0; i<45; i++ )
+    {
+        float h = map(ro + rd*t).x;
+        res = min( res, k*h/t );
+        t += clamp( h, 0.04, 0.1 );
+    }
+    return clamp(res,0.0,1.0);
+}
+*/

          
A => liminal_legacy/liminal/assets/shaders/lib/sh.glsl +49 -0
@@ 0,0 1,49 @@ 
+
+#ifndef M_DIVPI
+#define M_DIVPI 0.3183098861837907
+#endif
+
+vec4 sh_project(vec3 n) {
+    const float f1 = 0.488602511902919920;
+    return vec4(
+        0.282094791773878140,
+        vec3(-f1,f1,-f1) * n.yzx);
+}
+
+float sh_shade(vec4 vL, vec4 vN) {
+    return max(dot(vL, vN), 0.0) * M_DIVPI;
+}
+
+#define SHSharpness 1.0 // 2.0
+vec4 sh_irradiance_probe(vec4 v) {
+    const float sh_c0 = (2.0 - SHSharpness) * 1.0;
+    const float sh_c1 = SHSharpness * 2.0 / 3.0;
+    return vec4(v.x * sh_c0, v.yzw * sh_c1);
+}
+
+// from http://www.crytek.com/download/Light_Propagation_Volumes.pdf
+// input is direction and zonal harmonics coefficients
+vec4 sh_rotate(vec3 vcDir, vec2 vZHCoeffs) {
+    // compute sine and cosine of thetta angle
+    // beware of singularity when both x and y are 0 (no need to rotate at all)
+    vec2 theta12_cs = normalize(vcDir.xy);
+
+    // compute sine and cosine of phi angle
+    vec2 phi12_cs = vec2(
+        sqrt(1.0 - vcDir.z * vcDir.z),
+        vcDir.z
+    );
+    
+    return vec4(
+    // The first band is rotation-independent
+    vZHCoeffs.x,
+    // rotating the second band of SH
+    -vZHCoeffs.y * phi12_cs.x * theta12_cs.y,
+    vZHCoeffs.y * phi12_cs.y,
+    -vZHCoeffs.y * phi12_cs.x * theta12_cs.x
+    );
+}
+
+vec4 sh_project_hclobe(vec3 vcDir) {
+    return sh_rotate(vcDir, vec2(0.25, 0.5));
+}

          
A => liminal_legacy/liminal/assets/shaders/lib/snoise3.glsl +102 -0
@@ 0,0 1,102 @@ 
+//
+// Description : Array and textureless GLSL 2D/3D/4D simplex 
+//               noise functions.
+//      Author : Ian McEwan, Ashima Arts.
+//  Maintainer : ijm
+//     Lastmod : 20110822 (ijm)
+//     License : Copyright (C) 2011 Ashima Arts. All rights reserved.
+//               Distributed under the MIT License. See LICENSE file.
+//               https://github.com/ashima/webgl-noise
+// 
+
+vec3 mod289(vec3 x) {
+  return x - floor(x * (1.0 / 289.0)) * 289.0;
+}
+
+vec4 mod289(vec4 x) {
+  return x - floor(x * (1.0 / 289.0)) * 289.0;
+}
+
+vec4 permute(vec4 x) {
+     return mod289(((x*34.0)+1.0)*x);
+}
+
+vec4 taylorInvSqrt(vec4 r)
+{
+  return 1.79284291400159 - 0.85373472095314 * r;
+}
+
+float snoise(vec3 v)
+  { 
+  const vec2  C = vec2(1.0/6.0, 1.0/3.0) ;
+  const vec4  D = vec4(0.0, 0.5, 1.0, 2.0);
+
+// First corner
+  vec3 i  = floor(v + dot(v, C.yyy) );
+  vec3 x0 =   v - i + dot(i, C.xxx) ;
+
+// Other corners
+  vec3 g = step(x0.yzx, x0.xyz);
+  vec3 l = 1.0 - g;
+  vec3 i1 = min( g.xyz, l.zxy );
+  vec3 i2 = max( g.xyz, l.zxy );
+
+  //   x0 = x0 - 0.0 + 0.0 * C.xxx;
+  //   x1 = x0 - i1  + 1.0 * C.xxx;
+  //   x2 = x0 - i2  + 2.0 * C.xxx;
+  //   x3 = x0 - 1.0 + 3.0 * C.xxx;
+  vec3 x1 = x0 - i1 + C.xxx;
+  vec3 x2 = x0 - i2 + C.yyy; // 2.0*C.x = 1/3 = C.y
+  vec3 x3 = x0 - D.yyy;      // -1.0+3.0*C.x = -0.5 = -D.y
+
+// Permutations
+  i = mod289(i); 
+  vec4 p = permute( permute( permute( 
+             i.z + vec4(0.0, i1.z, i2.z, 1.0 ))
+           + i.y + vec4(0.0, i1.y, i2.y, 1.0 )) 
+           + i.x + vec4(0.0, i1.x, i2.x, 1.0 ));
+
+// Gradients: 7x7 points over a square, mapped onto an octahedron.
+// The ring size 17*17 = 289 is close to a multiple of 49 (49*6 = 294)
+  float n_ = 0.142857142857; // 1.0/7.0
+  vec3  ns = n_ * D.wyz - D.xzx;
+
+  vec4 j = p - 49.0 * floor(p * ns.z * ns.z);  //  mod(p,7*7)
+
+  vec4 x_ = floor(j * ns.z);
+  vec4 y_ = floor(j - 7.0 * x_ );    // mod(j,N)
+
+  vec4 x = x_ *ns.x + ns.yyyy;
+  vec4 y = y_ *ns.x + ns.yyyy;
+  vec4 h = 1.0 - abs(x) - abs(y);
+
+  vec4 b0 = vec4( x.xy, y.xy );
+  vec4 b1 = vec4( x.zw, y.zw );
+
+  //vec4 s0 = vec4(lessThan(b0,0.0))*2.0 - 1.0;
+  //vec4 s1 = vec4(lessThan(b1,0.0))*2.0 - 1.0;
+  vec4 s0 = floor(b0)*2.0 + 1.0;
+  vec4 s1 = floor(b1)*2.0 + 1.0;
+  vec4 sh = -step(h, vec4(0.0));
+
+  vec4 a0 = b0.xzyw + s0.xzyw*sh.xxyy ;
+  vec4 a1 = b1.xzyw + s1.xzyw*sh.zzww ;
+
+  vec3 p0 = vec3(a0.xy,h.x);
+  vec3 p1 = vec3(a0.zw,h.y);
+  vec3 p2 = vec3(a1.xy,h.z);
+  vec3 p3 = vec3(a1.zw,h.w);
+
+//Normalise gradients
+  vec4 norm = taylorInvSqrt(vec4(dot(p0,p0), dot(p1,p1), dot(p2, p2), dot(p3,p3)));
+  p0 *= norm.x;
+  p1 *= norm.y;
+  p2 *= norm.z;
+  p3 *= norm.w;
+
+// Mix final noise value
+  vec4 m = max(0.6 - vec4(dot(x0,x0), dot(x1,x1), dot(x2,x2), dot(x3,x3)), 0.0);
+  m = m * m;
+  return 42.0 * dot( m*m, vec4( dot(p0,x0), dot(p1,x1), 
+                                dot(p2,x2), dot(p3,x3) ) );
+  }
  No newline at end of file

          
A => liminal_legacy/liminal/assets/shaders/lib/srgb.glsl +12 -0
@@ 0,0 1,12 @@ 
+
+vec3 srgb2lin(vec3 color) {
+    return color * (color * (
+        color * 0.305306011 + 0.682171111) + 0.012522878);
+}
+
+vec3 lin2srgb(vec3 color) {
+    vec3 S1 = sqrt(color);
+    vec3 S2 = sqrt(S1);
+    vec3 S3 = sqrt(S2);
+    return 0.585122381 * S1 + 0.783140355 * S2 - 0.368262736 * S3;
+}

          
A => liminal_legacy/liminal/assets/shaders/lib/ycbcr.glsl +50 -0
@@ 0,0 1,50 @@ 
+
+// ycbcr.glsl
+// by leonard ritter (contact@leonard-ritter.com)
+// Duangle GbR
+// this file is in the public domain
+
+// Y'CbCr conversion matrices
+// after http://www.mir.com/DMG/ycbcr.html
+
+// we assume r,g,b,y = [0..1] and cb,cr = [-0.5..+0.5]
+// multiply with either matrix to convert
+
+// also have a look at 
+// http://www.pmavridis.com/research/fbcompression/#screen4-tab
+
+/*
+- uniform scales translate perfectly into Y'CbCr space, no change required
+- non-uniform scales (multiplying two colors in ycbcr space) requires a 
+  transformation which has been optimized in ycbcr_mul()
+- addition and subtraction of Y'CbCr values is equivalent to the
+  same operation in RGB space.
+*/
+
+const mat3 rgb2ycbcr = mat3(
+    0.299, -0.168736, 0.5, 
+    0.587, -0.331264, -0.418688,   
+    0.114, 0.5, -0.081312
+);
+
+const mat3 ycbcr2rgb = mat3(
+    1.0, 1.0, 1.0,
+    0.0, -0.344136, 1.772, 
+    1.402, -0.714136, 0.0
+); 
+
+vec3 ycbcr_clamp(vec3 c) {
+    return clamp(c, vec3(0.0, -0.5, -0.5), vec3(1.0, 0.5, 0.5));
+}
+
+const mat3 ycbcrmul = mat3(
+    0.144261, -0.081411, -0.102898,
+    0.427476, 1.530761, -0.304903,
+    0.887080, -0.500609,0.769275
+);
+
+vec3 ycbcr_mul(vec3 ya, vec3 y) {
+    vec3 p = ya * y;
+    vec3 q = ya * y.yzx + y * ya.yzx;
+    return (ycbcrmul * vec3(q.y, p.yz)) + vec3(p.x, q.xz);
+}

          
A => liminal_legacy/liminal/assets/shaders/lib/ycocg.glsl +111 -0
@@ 0,0 1,111 @@ 
+
+// based on
+// http://www.pmavridis.com/research/fbcompression/TheCompactYCoCgFB.pdf
+
+#if 1
+const mat3 rgb2ycocg = mat3(
+    0.25,  0.5, -0.25,
+     0.5,  0.0,  0.5,
+    0.25, -0.5, -0.25
+);
+const mat3 ycocg2rgb = mat3(
+     1.0, 1.0,  1.0,
+     1.0, 0.0, -1.0,
+    -1.0, 1.0, -1.0
+);
+
+vec3 rgb_to_ycocg(vec3 color) {
+    return rgb2ycocg * color;
+}
+
+vec3 ycocg_to_rgb(vec3 color) {
+    return clamp(ycocg2rgb * color, 0.0, 1.0);
+}
+#else
+vec3 rgb_to_ycocg(vec3 color) {
+    float Co = color.r - color.b;
+    float t = color.b + Co*0.5;
+    float Cg = color.g - t;
+    float Y = t + Cg * 0.5;
+    return vec3(Y, Co, Cg);
+}
+
+vec3 ycocg_to_rgb(vec3 color) {
+    float t = color.x - color.z*0.5;
+    float G = color.z + t;
+    float B = t - color.y*0.5;
+    float R = color.y + B;
+    return vec3(R, G, B);
+}
+#endif
+
+ivec2 ycocg_coord() {
+    return ivec2(gl_FragCoord.xy);
+}
+
+vec3 unpack_ycocg(bool even, vec2 color, float v) {
+    return even ? vec3(color.rg,v):vec3(color.r, v, color.g);
+}
+
+vec2 pack_ycocg(bool even, vec3 color) {
+    return even ? color.rg:color.rb;
+}
+
+
+bool ycocg_even(ivec2 crd) {
+    return (crd.x&1) ==(crd.y&1);
+}
+
+#define YCOCG_THRESH vec4(30.0/255.0)
+//Returns the missing chrominance (Co or Cg) of a pixel.
+//a1-a4 are the 4 neighbors of the center pixel a0.
+float ycocg_filter(vec2 a0, vec2 a1, vec2 a2, vec2 a3, vec2 a4)
+{
+    vec4 lum = vec4(a1.x, a2.x , a3.x, a4.x);
+    vec4 l2 = vec4(a1.y, a2.y, a3.y, a4.y);
+    vec4 w = 1.0-step(YCOCG_THRESH, abs(lum - a0.x));
+    float W = w.x + w.y + w.z + w.w;
+    //handle the special case where all the weights are zero
+    vec2 ww = (W==0.0)?vec2(1.0,1.0):vec2(w.x,W);
+    //w.x = (W==0.0)? 1.0:w.x; W = (W==0.0)? 1.0:W;
+    return (dot(vec4(ww.x,w.yzw),l2))/ww.y;
+}
+
+vec3 ycocg_texture(in sampler2D sampler, in vec2 uv, in bool even) {
+    vec2 y0 = texture(sampler, uv).rg;
+    vec2 y1 = textureOffset(sampler, uv, ivec2( 1, 0)).rg;
+    vec2 y2 = textureOffset(sampler, uv, ivec2( 0, 1)).rg;
+    vec2 y3 = textureOffset(sampler, uv, ivec2(-1, 0)).rg;
+    vec2 y4 = textureOffset(sampler, uv, ivec2( 0,-1)).rg;
+    return unpack_ycocg(even, y0.rg, ycocg_filter(y0.rg, y1, y2, y3, y4));
+}
+
+vec3 ycocg_texture2(in sampler2D sampler, in vec2 uv, in bool even, out vec3 ycr2) {
+    vec4 y0 = texture(sampler, uv);
+    vec4 y1 = textureOffset(sampler, uv, ivec2( 1, 0));
+    vec4 y2 = textureOffset(sampler, uv, ivec2( 0, 1));
+    vec4 y3 = textureOffset(sampler, uv, ivec2(-1, 0));
+    vec4 y4 = textureOffset(sampler, uv, ivec2( 0,-1));
+    ycr2 = unpack_ycocg(even, y0.ba, ycocg_filter(y0.ba, y1.ba, y2.ba, y3.ba, y4.ba));
+    return unpack_ycocg(even, y0.rg, ycocg_filter(y0.rg, y1.rg, y2.rg, y3.rg, y4.rg));
+}
+
+vec2 ycocg_frag_rgb8(bool even, in vec3 color) {
+    vec2 yc = pack_ycocg(even, rgb_to_ycocg(color));
+    yc.g += 0.5;
+    return yc;
+}
+
+vec3 ycocg_texture_rg8(in sampler2D sampler, in vec2 uv, in bool even) {
+    vec3 ycr = ycocg_texture(sampler, uv, even);
+    ycr.gb -= vec2(0.5);
+    return ycocg_to_rgb(ycr);
+}
+
+vec3 ycocg_texture_rgba8(in sampler2D sampler, in vec2 uv, in bool even, out vec3 ycr2) {
+    vec3 ycr = ycocg_texture2(sampler, uv, even, ycr2);
+    ycr.gb -= vec2(0.5);
+    ycr2.gb -= vec2(0.5);
+    ycr2 = ycocg_to_rgb(ycr2);
+    return ycocg_to_rgb(ycr);
+}

          
A => liminal_legacy/liminal/assets/shaders/nm/nm_frag.glsl +9 -0
@@ 0,0 1,9 @@ 
+
+#include "nm/nm_vars.glsl"
+
+vec3 get_mapped_normal(in sampler2D normal_map, in vec2 uv) { 
+    vec3 n = texture2D(normal_map, uv).rgb*2.0 - 1.0;
+    n.y *= -1.0;
+    return mtx_tangent * n;
+}
+

          
A => liminal_legacy/liminal/assets/shaders/nm/nm_vars.glsl +2 -0
@@ 0,0 1,2 @@ 
+
+varying mat3 mtx_tangent;

          
A => liminal_legacy/liminal/assets/shaders/nm/nm_vert.glsl +10 -0
@@ 0,0 1,10 @@ 
+
+#include "nm/nm_vars.glsl"
+
+void calc_mtx_tangent() {
+    mat3 mtx_mv3 = mat3(mtx_modelview);
+
+    vec3 tangent = mtx_mv3 * normalize(in_Tangent);
+    vec3 bitangent = mtx_mv3 * normalize(in_Bitangent);
+    mtx_tangent = mat3(tangent,bitangent,normal);
+}

          
A => liminal_legacy/liminal/assets/shaders/pom/pom_frag.glsl +27 -0
@@ 0,0 1,27 @@ 
+
+#include "pom/pom_vars.glsl"
+
+vec2 calc_pom_parallax_uv(in sampler2D height_map, in vec2 uv) {
+    float h0 = texture2D(height_map, uv - pom_parallax * 1.000).r;
+    float h1 = texture2D(height_map, uv - pom_parallax * 0.875).r;
+    float h2 = texture2D(height_map, uv - pom_parallax * 0.750).r;
+    float h3 = texture2D(height_map, uv - pom_parallax * 0.625).r;
+    float h4 = texture2D(height_map, uv - pom_parallax * 0.500).r;
+    float h5 = texture2D(height_map, uv - pom_parallax * 0.375).r;
+    float h6 = texture2D(height_map, uv - pom_parallax * 0.250).r;
+    float h7 = texture2D(height_map, uv - pom_parallax * 0.125).r;
+    
+    float x,y, xh, yh;
+    if      (h7 > 0.875) { x = 0.875; y = 1.000; xh = h7; yh = h7; }
+    else if (h6 > 0.750) { x = 0.750; y = 0.875; xh = h6; yh = h7; }
+    else if (h5 > 0.625) { x = 0.625; y = 0.750; xh = h5; yh = h6; }
+    else if (h4 > 0.500) { x = 0.500; y = 0.625; xh = h4; yh = h5; }
+    else if (h3 > 0.375) { x = 0.375; y = 0.500; xh = h3; yh = h4; }
+    else if (h2 > 0.250) { x = 0.250; y = 0.375; xh = h2; yh = h3; }
+    else if (h1 > 0.125) { x = 0.125; y = 0.250; xh = h1; yh = h2; }
+    else                 { x = 0.000; y = 0.125; xh = h0; yh = h1; }
+    
+    float fx = (x * (y - yh) - y * (x - xh)) / ((y - yh) - (x - xh));
+    
+    return uv - pom_parallax * (1.0 - fx);
+}

          
A => liminal_legacy/liminal/assets/shaders/pom/pom_vars.glsl +3 -0
@@ 0,0 1,3 @@ 
+
+varying vec2 pom_parallax;
+

          
A => liminal_legacy/liminal/assets/shaders/pom/pom_vert.glsl +24 -0
@@ 0,0 1,24 @@ 
+
+#include "pom/pom_vars.glsl"
+
+const float pom_height_scale = 0.5;
+const float pom_perspective_bias = 0.8;
+
+void calc_pom_parallax() {
+    mat3 mtx_mv3 = mat3(mtx_modelview);
+    
+    vec3 view = -vec3(mtx_modelview * gl_Vertex);
+    
+    vec3 vnormal = mtx_mv3 * normalize(cross(in_Tangent, in_Bitangent));
+    vec3 vtangent = mtx_mv3 * in_Tangent;
+    vec3 vbitangent = mtx_mv3 * in_Bitangent;
+    
+    vec3 viewts = transpose(mat3(vtangent, vbitangent, vnormal)) * normalize(view);
+    
+    vec2 parallax_dir = normalize(viewts.xy);
+    float parallax_len = -sqrt(1.0 - viewts.z * viewts.z) / viewts.z;
+    float parallax_bias = pom_perspective_bias + (1.0 - pom_perspective_bias) *
+        (2.0 * viewts.z - 1.0);
+    
+    pom_parallax = -parallax_dir * parallax_len * parallax_bias * pom_height_scale;
+} 

          
A => liminal_legacy/liminal/assets/shaders/ppfx/blit.glsl +11 -0
@@ 0,0 1,11 @@ 
+#include "std/std.glsl"
+#include "copy.glsl"
+
+#if FRAGMENT_SHADER
+
+uniform sampler2D fb_color0;
+
+void main() {
+    out_Color = texture(fb_color0, screen_texcoord);
+}
+#endif

          
A => liminal_legacy/liminal/assets/shaders/ppfx/blit_layers.glsl +63 -0
@@ 0,0 1,63 @@ 
+
+#ifndef INT_LAYER
+#define INT_LAYER 0
+#endif
+
+#ifndef FAKE_VERTEX_LAYER
+#define FAKE_VERTEX_LAYER 1
+#endif
+
+#if INT_LAYER
+#define LAYER_TYPE int
+#define LAYER_INIT 0
+#define DATA_QUAL flat
+#else
+#define LAYER_TYPE float
+#define LAYER_INIT 0.0
+#define DATA_QUAL
+#endif
+
+struct BlitLayerData {
+    LAYER_TYPE layer;
+};
+
+#if VERTEX_SHADER
+
+#if FAKE_VERTEX_LAYER
+DATA_QUAL out LAYER_TYPE vdata;
+#endif
+
+void main(void) {
+    gl_Position = vec4(in_Position.xy, 0.0, 1.0);
+#if FAKE_VERTEX_LAYER
+    vdata = LAYER_INIT;
+#endif
+}
+#elif GEOMETRY_SHADER
+layout(triangles) in;
+layout(triangle_strip, max_vertices = LAYER_GS_VERTEX_COUNT) out;
+
+#if FAKE_VERTEX_LAYER
+DATA_QUAL in LAYER_TYPE vdata[3];
+#endif
+DATA_QUAL out BlitLayerData data;
+
+void main() {
+  for (int k = 0; k < LAYER_COUNT; ++k) {
+        for(int i = 0; i < 3; ++i) {
+            gl_Layer = k;
+            // hack to fix buggy intel MESA driver: pass through
+            // fake layer attribute so fragment shader doesn't
+            // optimize it away.
+            data.layer =
+#if FAKE_VERTEX_LAYER
+                vdata[i] +
+#endif
+                LAYER_TYPE(k);
+            gl_Position = gl_in[i].gl_Position;
+            EmitVertex();
+        }
+        EndPrimitive();
+  }
+}
+#endif
  No newline at end of file

          
A => liminal_legacy/liminal/assets/shaders/ppfx/bloom_mixdown.glsl +43 -0
@@ 0,0 1,43 @@ 
+#include "std/std.glsl"
+
+#ifndef SCALE_WITH_DEPTH
+#define SCALE_WITH_DEPTH 0
+#endif
+
+#if SCALE_WITH_DEPTH
+#include "g/ppfx/view_ray.glsl"
+#else
+#include "copy.glsl"
+#endif
+
+
+#if FRAGMENT_SHADER
+
+uniform sampler2D fb_color0;
+uniform sampler2D bloom_color0;
+uniform sampler2D depth_texture;
+uniform float mix_factor = 0.5;
+uniform vec2 depth_range = vec2(100.0, 200.0); 
+
+void main() {
+    vec2 uv = screen_texcoord;
+#if SCALE_WITH_DEPTH
+    float depth = texture(depth_texture, uv).b;
+    float depth_intensity = clamp(
+        (length(view_ray * depth) - depth_range[0]) / (depth_range[1] - depth_range[0]), 0.0, 1.0);
+#endif
+
+#if SCALE_WITH_DEPTH
+    float f = mix(mix_factor, 1.0, depth_intensity);
+#else
+	float f = mix_factor;
+#endif
+
+    vec3 color = texture(fb_color0, uv).rgb;
+
+    color = mix(color, texture(bloom_color0, uv).rgb, f);
+    
+    out_Color = vec4(color, 1.0); 
+}
+
+#endif
  No newline at end of file

          
A => liminal_legacy/liminal/assets/shaders/ppfx/blur.glsl +76 -0
@@ 0,0 1,76 @@ 
+#include "std/std.glsl"
+#include "copy.glsl"
+
+#if FRAGMENT_SHADER
+
+#ifndef BLUR_ADD
+#define BLUR_ADD 0
+#endif
+
+#ifndef SCALE_UV
+#define SCALE_UV 0
+#endif
+
+#ifndef MIX_DEPTH
+#define MIX_DEPTH 0
+#endif
+
+#ifndef RESCALE
+#define RESCALE 0
+#endif
+
+#ifndef KERNEL_RADIUS
+#define KERNEL_RADIUS 3
+#endif
+
+uniform sampler2D fb_color0;
+uniform vec2 fb_texel;
+uniform vec2 fb_size;
+
+#if BLUR_ADD
+uniform sampler2D fb_add_color0;
+#endif
+
+#if SCALE_UV
+uniform vec2 aspect;
+#endif
+
+vec3 sample_blur_texture(vec2 uv, vec2 offset) {
+    return texture(fb_color0, uv + offset).rgb;
+}
+
+#define BLUR_TYPE vec3
+#include "lib/blur.glsl"
+
+void main() {
+    vec2 uv = screen_texcoord;
+    vec3 color;
+#if SCALE_UV
+    uv *= aspect;
+#endif
+#if KERNEL_RADIUS == 7     
+    color = blur_texture_fastgauss_7x7(uv, fb_texel, fb_size);
+#elif KERNEL_RADIUS == 5     
+    color = blur_texture_fastgauss_5x5(uv, fb_texel, fb_size);
+#elif KERNEL_RADIUS == 3
+    color = blur_texture_fastgauss_3x3(uv, fb_texel, fb_size);
+#elif KERNEL_RADIUS == 2
+    // gaussian 4x4, using bilinear filtering to reduce fetches
+    color  = texture(fb_color0, uv + 0.75*vec2( fb_texel.x, fb_texel.y)).rgb;
+    color += texture(fb_color0, uv + 0.75*vec2( fb_texel.x,-fb_texel.y)).rgb;
+    color += texture(fb_color0, uv + 0.75*vec2(-fb_texel.x,-fb_texel.y)).rgb;
+    color += texture(fb_color0, uv + 0.75*vec2(-fb_texel.x, fb_texel.y)).rgb;
+    color /= 4.0;
+#else    
+    color = texture(fb_color0, uv).rgb;
+#endif
+#if BLUR_ADD
+    color += texture(fb_add_color0, uv).rgb;
+#endif
+#ifdef SCALE_COLOR
+    color *= SCALE_COLOR;
+#endif
+    out_Color = vec4(color, 1.0); 
+}
+
+#endif
  No newline at end of file

          
A => liminal_legacy/liminal/assets/shaders/ppfx/copy.glsl +14 -0
@@ 0,0 1,14 @@ 
+noperspective varying vec2 window_xy;
+noperspective varying vec2 absolute_texcoord;
+noperspective varying vec2 screen_texcoord;
+
+#if VERTEX_SHADER
+
+void main(void) {
+    gl_Position = vec4(in_Position.xy, 0.0, 1.0);
+    window_xy = in_Position.xy;
+    absolute_texcoord = window_xy * 0.5 + 0.5;
+    screen_texcoord = absolute_texcoord * viewport_scale + viewport_offset;
+}
+
+#endif
  No newline at end of file

          
A => liminal_legacy/liminal/assets/shaders/ppfx/copy_luma_ds2.glsl +17 -0
@@ 0,0 1,17 @@ 
+#include "std/std.glsl"
+#include "copy.glsl"
+
+#if FRAGMENT_SHADER
+
+uniform sampler2D fb_color0;
+uniform vec2 fb_texel;
+
+void main() {
+    out_Color.r = 
+        (texture(fb_color0, absolute_texcoord + vec2(-fb_texel.x*0.5,-fb_texel.y*0.5)).r
+       + texture(fb_color0, absolute_texcoord + vec2( fb_texel.x*0.5,-fb_texel.y*0.5)).r
+       + texture(fb_color0, absolute_texcoord + vec2(-fb_texel.x*0.5, fb_texel.y*0.5)).r
+       + texture(fb_color0, absolute_texcoord + vec2( fb_texel.x*0.5, fb_texel.y*0.5)).r) / 4.0;
+}
+
+#endif
  No newline at end of file

          
A => liminal_legacy/liminal/assets/shaders/ppfx/debug_color.glsl +15 -0
@@ 0,0 1,15 @@ 
+#include "std/std.glsl"
+#include "copy.glsl"
+
+#if FRAGMENT_SHADER
+
+uniform sampler2D fb_color0;
+
+void main() {
+    vec2 uv = screen_texcoord;
+    uv = ((uv - viewport_offset) / viewport_scale);
+    
+    out_Color = vec4(mod(texture(fb_color0, uv).rgb, vec3(2.0)), 1.0);
+}
+
+#endif
  No newline at end of file

          
A => liminal_legacy/liminal/assets/shaders/ppfx/debug_layer_color.glsl +22 -0
@@ 0,0 1,22 @@ 
+#include "std/std.glsl"
+#include "copy.glsl"
+
+#if FRAGMENT_SHADER
+
+uniform sampler2DArray fb_color0;
+uniform float layers;
+
+const float stepsize = 100.0;
+
+void main() {
+    vec2 uv = screen_texcoord;
+    uv = ((uv - viewport_offset) / viewport_scale);
+
+    float layer = uv.x * layers;
+    layer -= mod(layer, 1.0);
+    vec3 rgb = mod(texture(fb_color0, vec3(uv,layer)).rgb, vec3(2.0));
+
+    out_Color = vec4(rgb, 1.0);
+}
+
+#endif

          
A => liminal_legacy/liminal/assets/shaders/ppfx/debug_layer_depth.glsl +22 -0
@@ 0,0 1,22 @@ 
+#include "std/std.glsl"
+#include "copy.glsl"
+
+#if FRAGMENT_SHADER
+
+uniform sampler2DArray fb_color0;
+uniform float layers;
+
+const float stepsize = 100.0;
+
+void main() {
+    vec2 uv = screen_texcoord;
+    uv = ((uv - viewport_offset) / viewport_scale);
+
+    float layer = uv.x * layers;
+    layer -= mod(layer, 1.0);
+    float d = mod(texture(fb_color0, vec3(uv,layer)).r * stepsize, 1.0);
+
+    out_Color = vec4(vec3(d), 1.0);
+}
+
+#endif
  No newline at end of file

          
A => liminal_legacy/liminal/assets/shaders/ppfx/debug_material.glsl +175 -0
@@ 0,0 1,175 @@ 
+#include "std/std.glsl"
+#include "copy.glsl"
+
+#if FRAGMENT_SHADER
+
+#include "lib/brdf.glsl"
+#include "lib/srgb.glsl"
+#include "lib/ibl_sample.glsl"
+
+uniform sampler2D tex_mat0;
+uniform sampler2D tex_mat1;
+uniform samplerCube envmap;
+uniform vec3 light_pos = vec3(0.75, 0.75, 0.5);
+uniform float uv_scale = 1.0;
+uniform float ao_factor = 1.0;
+uniform vec3 light_color = vec3(1.0);
+uniform float ambient_factor = 1.0;
+uniform float roughness = 0.0;
+uniform float metallic = 0.0;
+uniform float emissive_factor = 0.5;
+uniform float emissive_offset = 0.5;
+uniform vec3 albedo_color = vec3(1.0);
+uniform vec3 glow_color = vec3(1.0);
+uniform bool show_objid = false;
+uniform bool use_metallic = true;
+uniform bool use_roughness = true;
+uniform int render_mode = 0;
+uniform float sharpness = 1.0;
+
+mat3 mtx_rot(float a) {
+    float sa = sin(a);
+    float ca = cos(a);
+    return mat3(
+         ca, 0.0, -sa,
+        0.0, 1.0, 0.0,
+         sa, 0.0,  ca);
+}
+
+vec3 cubemap_vector(in vec3 v) {
+    return vec3(v.xz, -v.y);
+} 
+
+vec3 color_from_objid(int i) {
+    switch(i) {
+        case 1: return vec3(1.0,0.1,0.1);
+        case 2: return vec3(0.1,1.0,0.1);
+        case 3: return vec3(1.0,1.0,0.1);
+        case 4: return vec3(0.1,0.1,1.0);
+        case 5: return vec3(1.0,0.1,1.0);
+        case 6: return vec3(0.1,1.0,1.0);
+        default: return vec3(1.0);  
+    }
+}
+
+vec3 ff_filmic_gamma3(vec3 linear) {
+    vec3 x = max(vec3(0.0), linear-0.004);
+    return (x*(x*6.2+0.5))/(x*(x*6.2+1.7)+0.06);
+}
+
+vec4 render_final(vec2 uv, vec4 mat0, vec4 mat1) {
+    vec3 position = vec3(uv, 0.0);
+    
+    mat0.xy = mat0.xy*2.0 - 1.0;    
+    vec3 normal = normalize(vec3(mat0.xy, sqrt(1.0-mat0.x*mat0.x-mat0.y*mat0.y)));
+    
+    vec3 l = normalize(light_pos - position);
+    vec3 v = normalize(vec3(0.5, 0.5, 1.0) - position);
+    //vec3 v = vec3(0.0,0.0,1.0);
+    
+    //float C1 = max(1.0, sharpness / fwidth(uv.(max(abs(dFdx(uv.s)), abs(dFdy(uv.t))) * 32.0));
+    
+    vec2 fw = fwidth(uv);
+    float C1 = max(1.0, sharpness / max(fw.x,fw.y));
+    float C0 = 0.5*(1.0-C1);
+    
+    float cos_Ol = max(0.0, dot(normal, l));
+    float ao = mix(1.0,mat1.x,ao_factor);
+    float e = clamp(C1*mat1.y + C0, 0.0, 1.0);
+    float r, m;
+    if (use_roughness)
+        r = mat1.z;
+    else
+        r = 0.5;
+    if (use_metallic)
+        m = mat1.w;
+    else
+        m = 0.5;
+    
+    r = clamp(roughness + r, 0.0, 1.0);
+    m = clamp(metallic + m, 0.0, 1.0);
+    
+    vec3 light = vec3(cos_Ol);
+
+    vec3 base_color;
+    if (show_objid)
+        base_color = color_from_objid(int(mat0.w*255.0));
+    else
+        base_color = mix(albedo_color, glow_color, e);
+    
+    mat3 mtx = mtx_rot(time * 0.1);
+
+    // setup specular+diffuse IBL
+    ibla = IBLAttributes(
+        m,
+        r,
+        base_color,
+        mtx * v,
+        mtx * normal
+    );
+    // diffuse IBL      
+    vec3 ibl_diff = vec3(0.0);
+    vec3 ibl_spec = ibl_sample_specular_diffuse(envmap, ibl_diff);
+    
+    brdfa.metallic = m;
+    brdfa.roughness = r;
+    brdfa.base_color = base_color;
+    brdfa.cos_Ol = cos_Ol;
+    brdfa.l = l;
+    brdfa.v = v;
+    brdfa.n = normal;
+    vec3 color = brdf_evaluate() * cos_Ol;
+    
+    color *= light * light_color;
+    //color += base_color * 0.05f * (1.0f - m);
+    color += (ibl_diff * (1.0 - metallic) + ibl_spec / mix(M_PI, 1.0, m)) * ambient_factor * 0.05f;
+    
+    color *= ao;
+    
+    color += base_color * e * emissive_factor * light;
+    
+    color = clamp(ff_filmic_gamma3(color*2.0), 0.0, 1.0);    
+    return vec4(color, 1.0);
+}
+
+void main() {
+    vec2 uv = screen_texcoord;
+    uv = ((uv - viewport_offset) / viewport_scale);
+
+    uv *= uv_scale;
+
+    // Nx, Ny, Depth, OBJID
+    vec4 mat0 = texture(tex_mat0, uv);
+    // AO, Emissive, Roughness, Metallic
+    vec4 mat1 = texture(tex_mat1, uv);
+    
+    switch(render_mode) {
+        case 0: {
+            out_Color = render_final(uv, mat0, mat1);
+        } break;
+        case 1: {
+            out_Color = vec4(mat0.xy, 0.0, 1.0);
+        } break;
+        case 2: {
+            out_Color = vec4(vec3(mat0.z), 1.0);
+        } break;
+        case 3: {
+            out_Color = vec4(vec3(mat0.w), 1.0);
+        } break;
+        case 4: {
+            out_Color = vec4(vec3(mat1.x), 1.0);
+        } break;
+        case 5: {
+            out_Color = vec4(vec3(mat1.y), 1.0);
+        } break;
+        case 6: {
+            out_Color = vec4(vec3(mat1.z), 1.0);
+        } break;
+        case 7: {
+            out_Color = vec4(vec3(mat1.w), 1.0);
+        } break;
+        default: break;
+    }
+}
+
+#endif
  No newline at end of file

          
A => liminal_legacy/liminal/assets/shaders/ppfx/debug_normal.glsl +18 -0
@@ 0,0 1,18 @@ 
+#include "std/std.glsl"
+#include "copy.glsl"
+#include "lib/normal_codec.glsl"
+
+#if FRAGMENT_SHADER
+
+uniform sampler2D fb_color0;
+
+void main() {
+    vec2 uv = screen_texcoord;
+    uv = ((uv - viewport_offset) / viewport_scale);
+
+    vec3 normal = decode_normal(texture(fb_color0, uv).rg)*0.5+0.5;
+
+    out_Color = vec4(normal, 1.0);
+}
+
+#endif
  No newline at end of file

          
A => liminal_legacy/liminal/assets/shaders/ppfx/debug_velocity.glsl +19 -0
@@ 0,0 1,19 @@ 
+#include "std/std.glsl"
+#include "copy.glsl"
+
+#if FRAGMENT_SHADER
+
+uniform isampler2D fb_color0;
+
+void main() {
+    vec2 uv = screen_texcoord;
+    uv = ((uv - viewport_offset) / viewport_scale);
+
+    vec2 velocity = vec2(texture(fb_color0, uv).rg) / 127.0;
+    // exaggerate
+    velocity *= 100.0;
+
+    out_Color = vec4(velocity*0.5 + 0.5, 0.0, 1.0);
+}
+
+#endif
  No newline at end of file

          
A => liminal_legacy/liminal/assets/shaders/ppfx/debug_ycocg.glsl +30 -0
@@ 0,0 1,30 @@ 
+#include "std/std.glsl"
+#include "copy.glsl"
+#include "lib/ycocg.glsl"
+
+#if FRAGMENT_SHADER
+
+uniform sampler2D fb_color0;
+uniform float fb_texel;
+uniform vec2 fb_size;
+
+
+void main() {
+    vec2 uv = screen_texcoord;
+    uv = ((uv - viewport_offset) / viewport_scale);
+    
+    ivec2 coord = ivec2(uv * fb_size);
+    bool even = ycocg_even(coord);
+    vec2 fuv = (vec2(coord)+0.5) / fb_size;
+    
+    vec3 uvlight;
+    vec3 color = ycocg_texture_rgba8(fb_color0, fuv, even, uvlight);
+    
+    if (uv.x < 0.5) {
+        out_Color = vec4(color, 1.0);
+    } else {
+        out_Color = vec4(uvlight, 1.0);
+    }
+}
+
+#endif
  No newline at end of file

          
A => liminal_legacy/liminal/assets/shaders/ppfx/derivative_map.glsl +26 -0
@@ 0,0 1,26 @@ 
+#include "std/std.glsl"
+#include "ppfx/copy.glsl"
+
+#if FRAGMENT_SHADER
+
+uniform sampler2D height_map;
+
+void main() {
+    vec2 uv = absolute_texcoord;
+    
+#if 1    
+    vec2 d = vec2(1.0) / vec2(textureSize(height_map, 0));
+    vec2 dx = vec2(d.x, 0.0);
+    vec2 dy = vec2(0.0, d.y);
+#else
+    vec2 dx = dFdx(uv);
+    vec2 dy = dFdy(uv);
+#endif
+
+    float x = texture(height_map, uv).r;
+    float s = texture(height_map, uv + dx).r - x;
+    float t = texture(height_map, uv + dy).r - x;
+    
+    out_Color = vec4(vec2(s, t)*0.5 + 0.5, 0.0, 0.0);
+}
+#endif

          
A => liminal_legacy/liminal/assets/shaders/ppfx/fxaa3_11.glsl +2246 -0
@@ 0,0 1,2246 @@ 
+
+#include "std/std.glsl"
+
+#if VERTEX_SHADER
+
+noperspective varying vec2 texcoord; 
+
+void main(void) {
+    gl_Position = vec4(in_Position.xy, 0.0, 1.0);
+    texcoord = (in_Position.xy + 1.0) / 2.0;
+    texcoord = texcoord * viewport_scale + viewport_offset;
+}
+
+#elif FRAGMENT_SHADER
+// modified shader with included lookup
+
+// custom configuration for beige
+#define FXAA_PC 1
+#define FXAA_GLSL_130 1
+#define FXAA_GREEN_AS_LUMA 0
+#define FXAA_GATHER4_ALPHA 0
+
+#if VIDEO_QUALITY > VQ_HIGHEST
+#define FXAA_QUALITY_PRESET 39
+#elif VIDEO_QUALITY >= VQ_HIGHEST
+#define FXAA_QUALITY_PRESET 29
+#elif VIDEO_QUALITY >= VQ_HIGHER
+#define FXAA_QUALITY_PRESET 20
+#else
+#define FXAA_QUALITY_PRESET 12
+#endif
+
+/*============================================================================
+
+
+                    NVIDIA FXAA 3.11 by TIMOTHY LOTTES
+
+
+------------------------------------------------------------------------------
+COPYRIGHT (C) 2010, 2011 NVIDIA CORPORATION. ALL RIGHTS RESERVED.
+------------------------------------------------------------------------------
+TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, THIS SOFTWARE IS PROVIDED
+*AS IS* AND NVIDIA AND ITS SUPPLIERS DISCLAIM ALL WARRANTIES, EITHER EXPRESS
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT SHALL NVIDIA
+OR ITS SUPPLIERS BE LIABLE FOR ANY SPECIAL, INCIDENTAL, INDIRECT, OR
+CONSEQUENTIAL DAMAGES WHATSOEVER (INCLUDING, WITHOUT LIMITATION, DAMAGES FOR
+LOSS OF BUSINESS PROFITS, BUSINESS INTERRUPTION, LOSS OF BUSINESS INFORMATION,
+OR ANY OTHER PECUNIARY LOSS) ARISING OUT OF THE USE OF OR INABILITY TO USE
+THIS SOFTWARE, EVEN IF NVIDIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGES.
+
+------------------------------------------------------------------------------
+                           INTEGRATION CHECKLIST
+------------------------------------------------------------------------------
+(1.)
+In the shader source, setup defines for the desired configuration.
+When providing multiple shaders (for different presets),
+simply setup the defines differently in multiple files.
+Example,
+
+  #define FXAA_PC 1
+  #define FXAA_HLSL_5 1
+  #define FXAA_QUALITY_PRESET 12
+
+Or,
+
+  #define FXAA_360 1
+  
+Or,
+
+  #define FXAA_PS3 1
+  
+Etc.
+
+(2.)
+Then include this file,
+
+  #include "Fxaa3_11.h"
+
+(3.)
+Then call the FXAA pixel shader from within your desired shader.
+Look at the FXAA Quality FxaaPixelShader() for docs on inputs.
+As for FXAA 3.11 all inputs for all shaders are the same 
+to enable easy porting between platforms.
+
+  return FxaaPixelShader(...);
+
+(4.)
+Insure pass prior to FXAA outputs RGBL (see next section).
+Or use,
+
+  #define FXAA_GREEN_AS_LUMA 1
+
+(5.)
+Setup engine to provide the following constants
+which are used in the FxaaPixelShader() inputs,
+
+  FxaaFloat2 fxaaQualityRcpFrame,
+  FxaaFloat4 fxaaConsoleRcpFrameOpt,
+  FxaaFloat4 fxaaConsoleRcpFrameOpt2,
+  FxaaFloat4 fxaaConsole360RcpFrameOpt2,
+  FxaaFloat fxaaQualitySubpix,
+  FxaaFloat fxaaQualityEdgeThreshold,
+  FxaaFloat fxaaQualityEdgeThresholdMin,
+  FxaaFloat fxaaConsoleEdgeSharpness,
+  FxaaFloat fxaaConsoleEdgeThreshold,
+  FxaaFloat fxaaConsoleEdgeThresholdMin,
+  FxaaFloat4 fxaaConsole360ConstDir
+
+Look at the FXAA Quality FxaaPixelShader() for docs on inputs.
+
+(6.)
+Have FXAA vertex shader run as a full screen triangle,
+and output "pos" and "fxaaConsolePosPos" 
+such that inputs in the pixel shader provide,
+
+  // {xy} = center of pixel
+  FxaaFloat2 pos,
+
+  // {xy__} = upper left of pixel
+  // {__zw} = lower right of pixel
+  FxaaFloat4 fxaaConsolePosPos,
+
+(7.)
+Insure the texture sampler(s) used by FXAA are set to bilinear filtering.
+
+
+------------------------------------------------------------------------------
+                    INTEGRATION - RGBL AND COLORSPACE
+------------------------------------------------------------------------------
+FXAA3 requires RGBL as input unless the following is set, 
+
+  #define FXAA_GREEN_AS_LUMA 1
+
+In which case the engine uses green in place of luma,
+and requires RGB input is in a non-linear colorspace.
+
+RGB should be LDR (low dynamic range).
+Specifically do FXAA after tonemapping.
+
+RGB data as returned by a texture fetch can be non-linear,
+or linear when FXAA_GREEN_AS_LUMA is not set.
+Note an "sRGB format" texture counts as linear,
+because the result of a texture fetch is linear data.
+Regular "RGBA8" textures in the sRGB colorspace are non-linear.
+
+If FXAA_GREEN_AS_LUMA is not set,
+luma must be stored in the alpha channel prior to running FXAA.
+This luma should be in a perceptual space (could be gamma 2.0).
+Example pass before FXAA where output is gamma 2.0 encoded,
+
+  color.rgb = ToneMap(color.rgb); // linear color output
+  color.rgb = sqrt(color.rgb);    // gamma 2.0 color output
+  return color;
+
+To use FXAA,
+
+  color.rgb = ToneMap(color.rgb);  // linear color output
+  color.rgb = sqrt(color.rgb);     // gamma 2.0 color output
+  color.a = dot(color.rgb, FxaaFloat3(0.299, 0.587, 0.114)); // compute luma
+  return color;
+
+Another example where output is linear encoded,
+say for instance writing to an sRGB formated render target,
+where the render target does the conversion back to sRGB after blending,
+
+  color.rgb = ToneMap(color.rgb); // linear color output
+  return color;
+
+To use FXAA,
+
+  color.rgb = ToneMap(color.rgb); // linear color output
+  color.a = sqrt(dot(color.rgb, FxaaFloat3(0.299, 0.587, 0.114))); // compute luma
+  return color;
+
+Getting luma correct is required for the algorithm to work correctly.
+
+
+------------------------------------------------------------------------------
+                          BEING LINEARLY CORRECT?
+------------------------------------------------------------------------------
+Applying FXAA to a framebuffer with linear RGB color will look worse.
+This is very counter intuitive, but happends to be true in this case.
+The reason is because dithering artifacts will be more visiable 
+in a linear colorspace.
+
+
+------------------------------------------------------------------------------
+                             COMPLEX INTEGRATION
+------------------------------------------------------------------------------
+Q. What if the engine is blending into RGB before wanting to run FXAA?
+
+A. In the last opaque pass prior to FXAA,
+   have the pass write out luma into alpha.
+   Then blend into RGB only.
+   FXAA should be able to run ok
+   assuming the blending pass did not any add aliasing.
+   This should be the common case for particles and common blending passes.
+
+A. Or use FXAA_GREEN_AS_LUMA.
+
+============================================================================*/
+
+/*============================================================================
+
+                             INTEGRATION KNOBS
+
+============================================================================*/
+//
+// FXAA_PS3 and FXAA_360 choose the console algorithm (FXAA3 CONSOLE).
+// FXAA_360_OPT is a prototype for the new optimized 360 version.
+//
+// 1 = Use API.
+// 0 = Don't use API.
+//
+/*--------------------------------------------------------------------------*/
+#ifndef FXAA_PS3
+    #define FXAA_PS3 0
+#endif
+/*--------------------------------------------------------------------------*/
+#ifndef FXAA_360
+    #define FXAA_360 0
+#endif
+/*--------------------------------------------------------------------------*/
+#ifndef FXAA_360_OPT
+    #define FXAA_360_OPT 0
+#endif
+/*==========================================================================*/
+#ifndef FXAA_PC
+    //
+    // FXAA Quality
+    // The high quality PC algorithm.
+    //
+    #define FXAA_PC 0
+#endif
+/*--------------------------------------------------------------------------*/
+#ifndef FXAA_PC_CONSOLE
+    //
+    // The console algorithm for PC is included
+    // for developers targeting really low spec machines.
+    // Likely better to just run FXAA_PC, and use a really low preset.
+    //
+    #define FXAA_PC_CONSOLE 0
+#endif
+/*--------------------------------------------------------------------------*/
+#ifndef FXAA_GLSL_120
+    #define FXAA_GLSL_120 0
+#endif
+/*--------------------------------------------------------------------------*/
+#ifndef FXAA_GLSL_130
+    #define FXAA_GLSL_130 0
+#endif
+/*--------------------------------------------------------------------------*/
+#ifndef FXAA_HLSL_3
+    #define FXAA_HLSL_3 0
+#endif
+/*--------------------------------------------------------------------------*/
+#ifndef FXAA_HLSL_4
+    #define FXAA_HLSL_4 0
+#endif
+/*--------------------------------------------------------------------------*/
+#ifndef FXAA_HLSL_5
+    #define FXAA_HLSL_5 0
+#endif
+/*==========================================================================*/
+#ifndef FXAA_GREEN_AS_LUMA
+    //
+    // For those using non-linear color,
+    // and either not able to get luma in alpha, or not wanting to,
+    // this enables FXAA to run using green as a proxy for luma.
+    // So with this enabled, no need to pack luma in alpha.
+    //
+    // This will turn off AA on anything which lacks some amount of green.
+    // Pure red and blue or combination of only R and B, will get no AA.
+    //
+    // Might want to lower the settings for both,
+    //    fxaaConsoleEdgeThresholdMin
+    //    fxaaQualityEdgeThresholdMin
+    // In order to insure AA does not get turned off on colors 
+    // which contain a minor amount of green.
+    //
+    // 1 = On.
+    // 0 = Off.
+    //
+    #define FXAA_GREEN_AS_LUMA 0
+#endif
+/*--------------------------------------------------------------------------*/
+#ifndef FXAA_EARLY_EXIT
+    //
+    // Controls algorithm's early exit path.
+    // On PS3 turning this ON adds 2 cycles to the shader.
+    // On 360 turning this OFF adds 10ths of a millisecond to the shader.
+    // Turning this off on console will result in a more blurry image.
+    // So this defaults to on.
+    //
+    // 1 = On.
+    // 0 = Off.
+    //
+    #define FXAA_EARLY_EXIT 1
+#endif
+/*--------------------------------------------------------------------------*/
+#ifndef FXAA_DISCARD
+    //
+    // Only valid for PC OpenGL currently.
+    // Probably will not work when FXAA_GREEN_AS_LUMA = 1.
+    //
+    // 1 = Use discard on pixels which don't need AA.
+    //     For APIs which enable concurrent TEX+ROP from same surface.
+    // 0 = Return unchanged color on pixels which don't need AA.
+    //
+    #define FXAA_DISCARD 0
+#endif
+/*--------------------------------------------------------------------------*/
+#ifndef FXAA_FAST_PIXEL_OFFSET
+    //
+    // Used for GLSL 120 only.
+    //
+    // 1 = GL API supports fast pixel offsets
+    // 0 = do not use fast pixel offsets
+    //
+    #ifdef GL_EXT_gpu_shader4
+        #define FXAA_FAST_PIXEL_OFFSET 1
+    #endif
+    #ifdef GL_NV_gpu_shader5
+        #define FXAA_FAST_PIXEL_OFFSET 1
+    #endif
+    #ifdef GL_ARB_gpu_shader5
+        #define FXAA_FAST_PIXEL_OFFSET 1
+    #endif
+    #ifndef FXAA_FAST_PIXEL_OFFSET
+        #define FXAA_FAST_PIXEL_OFFSET 0
+    #endif
+#endif
+/*--------------------------------------------------------------------------*/
+#ifndef FXAA_GATHER4_ALPHA
+    //
+    // 1 = API supports gather4 on alpha channel.
+    // 0 = API does not support gather4 on alpha channel.
+    //
+    #if (FXAA_HLSL_5 == 1)
+        #define FXAA_GATHER4_ALPHA 1
+    #endif
+    #ifdef GL_ARB_gpu_shader5
+        #define FXAA_GATHER4_ALPHA 1
+    #endif
+    #ifdef GL_NV_gpu_shader5
+        #define FXAA_GATHER4_ALPHA 1
+    #endif
+    #ifndef FXAA_GATHER4_ALPHA
+        #define FXAA_GATHER4_ALPHA 0
+    #endif
+#endif
+
+/*============================================================================
+                      FXAA CONSOLE PS3 - TUNING KNOBS
+============================================================================*/
+#ifndef FXAA_CONSOLE_PS3_EDGE_SHARPNESS
+    //
+    // Consoles the sharpness of edges on PS3 only.
+    // Non-PS3 tuning is done with shader input.
+    //
+    // Due to the PS3 being ALU bound,
+    // there are only two safe values here: 4 and 8.
+    // These options use the shaders ability to a free *|/ by 2|4|8.
+    //
+    // 8.0 is sharper
+    // 4.0 is softer
+    // 2.0 is really soft (good for vector graphics inputs)
+    //
+    #if 1
+        #define FXAA_CONSOLE_PS3_EDGE_SHARPNESS 8.0
+    #endif
+    #if 0
+        #define FXAA_CONSOLE_PS3_EDGE_SHARPNESS 4.0
+    #endif
+    #if 0
+        #define FXAA_CONSOLE_PS3_EDGE_SHARPNESS 2.0
+    #endif
+#endif
+/*--------------------------------------------------------------------------*/
+#ifndef FXAA_CONSOLE_PS3_EDGE_THRESHOLD
+    //
+    // Only effects PS3.
+    // Non-PS3 tuning is done with shader input.
+    //
+    // The minimum amount of local contrast required to apply algorithm.
+    // The console setting has a different mapping than the quality setting.
+    //
+    // This only applies when FXAA_EARLY_EXIT is 1.
+    //
+    // Due to the PS3 being ALU bound,
+    // there are only two safe values here: 0.25 and 0.125.
+    // These options use the shaders ability to a free *|/ by 2|4|8.
+    //
+    // 0.125 leaves less aliasing, but is softer
+    // 0.25 leaves more aliasing, and is sharper
+    //
+    #if 1
+        #define FXAA_CONSOLE_PS3_EDGE_THRESHOLD 0.125
+    #else
+        #define FXAA_CONSOLE_PS3_EDGE_THRESHOLD 0.25
+    #endif
+#endif
+
+/*============================================================================
+                        FXAA QUALITY - TUNING KNOBS
+------------------------------------------------------------------------------
+NOTE the other tuning knobs are now in the shader function inputs!
+============================================================================*/
+#ifndef FXAA_QUALITY_PRESET
+    //
+    // Choose the quality preset.
+    // This needs to be compiled into the shader as it effects code.
+    // Best option to include multiple presets is to 
+    // in each shader define the preset, then include this file.
+    // 
+    // OPTIONS
+    // -----------------------------------------------------------------------
+    // 10 to 15 - default medium dither (10=fastest, 15=highest quality)
+    // 20 to 29 - less dither, more expensive (20=fastest, 29=highest quality)
+    // 39       - no dither, very expensive 
+    //
+    // NOTES
+    // -----------------------------------------------------------------------
+    // 12 = slightly faster then FXAA 3.9 and higher edge quality (default)
+    // 13 = about same speed as FXAA 3.9 and better than 12
+    // 23 = closest to FXAA 3.9 visually and performance wise
+    //  _ = the lowest digit is directly related to performance
+    // _  = the highest digit is directly related to style
+    // 
+    #define FXAA_QUALITY_PRESET 12
+#endif
+
+
+/*============================================================================
+
+                           FXAA QUALITY - PRESETS
+
+============================================================================*/
+
+/*============================================================================
+                     FXAA QUALITY - MEDIUM DITHER PRESETS
+============================================================================*/
+#if (FXAA_QUALITY_PRESET == 10)
+    #define FXAA_QUALITY_PS 3
+    #define FXAA_QUALITY_P0 1.5
+    #define FXAA_QUALITY_P1 3.0
+    #define FXAA_QUALITY_P2 12.0
+#endif
+/*--------------------------------------------------------------------------*/
+#if (FXAA_QUALITY_PRESET == 11)
+    #define FXAA_QUALITY_PS 4
+    #define FXAA_QUALITY_P0 1.0
+    #define FXAA_QUALITY_P1 1.5
+    #define FXAA_QUALITY_P2 3.0
+    #define FXAA_QUALITY_P3 12.0
+#endif
+/*--------------------------------------------------------------------------*/
+#if (FXAA_QUALITY_PRESET == 12)
+    #define FXAA_QUALITY_PS 5
+    #define FXAA_QUALITY_P0 1.0
+    #define FXAA_QUALITY_P1 1.5
+    #define FXAA_QUALITY_P2 2.0
+    #define FXAA_QUALITY_P3 4.0
+    #define FXAA_QUALITY_P4 12.0
+#endif
+/*--------------------------------------------------------------------------*/
+#if (FXAA_QUALITY_PRESET == 13)
+    #define FXAA_QUALITY_PS 6
+    #define FXAA_QUALITY_P0 1.0
+    #define FXAA_QUALITY_P1 1.5
+    #define FXAA_QUALITY_P2 2.0
+    #define FXAA_QUALITY_P3 2.0
+    #define FXAA_QUALITY_P4 4.0
+    #define FXAA_QUALITY_P5 12.0
+#endif
+/*--------------------------------------------------------------------------*/
+#if (FXAA_QUALITY_PRESET == 14)
+    #define FXAA_QUALITY_PS 7
+    #define FXAA_QUALITY_P0 1.0
+    #define FXAA_QUALITY_P1 1.5
+    #define FXAA_QUALITY_P2 2.0
+    #define FXAA_QUALITY_P3 2.0
+    #define FXAA_QUALITY_P4 2.0
+    #define FXAA_QUALITY_P5 4.0
+    #define FXAA_QUALITY_P6 12.0
+#endif
+/*--------------------------------------------------------------------------*/
+#if (FXAA_QUALITY_PRESET == 15)
+    #define FXAA_QUALITY_PS 8
+    #define FXAA_QUALITY_P0 1.0
+    #define FXAA_QUALITY_P1 1.5
+    #define FXAA_QUALITY_P2 2.0
+    #define FXAA_QUALITY_P3 2.0
+    #define FXAA_QUALITY_P4 2.0
+    #define FXAA_QUALITY_P5 2.0
+    #define FXAA_QUALITY_P6 4.0
+    #define FXAA_QUALITY_P7 12.0
+#endif
+
+/*============================================================================
+                     FXAA QUALITY - LOW DITHER PRESETS
+============================================================================*/
+#if (FXAA_QUALITY_PRESET == 20)
+    #define FXAA_QUALITY_PS 3
+    #define FXAA_QUALITY_P0 1.5
+    #define FXAA_QUALITY_P1 2.0
+    #define FXAA_QUALITY_P2 8.0
+#endif
+/*--------------------------------------------------------------------------*/
+#if (FXAA_QUALITY_PRESET == 21)
+    #define FXAA_QUALITY_PS 4
+    #define FXAA_QUALITY_P0 1.0
+    #define FXAA_QUALITY_P1 1.5
+    #define FXAA_QUALITY_P2 2.0
+    #define FXAA_QUALITY_P3 8.0
+#endif
+/*--------------------------------------------------------------------------*/
+#if (FXAA_QUALITY_PRESET == 22)
+    #define FXAA_QUALITY_PS 5
+    #define FXAA_QUALITY_P0 1.0
+    #define FXAA_QUALITY_P1 1.5
+    #define FXAA_QUALITY_P2 2.0
+    #define FXAA_QUALITY_P3 2.0
+    #define FXAA_QUALITY_P4 8.0
+#endif
+/*--------------------------------------------------------------------------*/
+#if (FXAA_QUALITY_PRESET == 23)
+    #define FXAA_QUALITY_PS 6
+    #define FXAA_QUALITY_P0 1.0
+    #define FXAA_QUALITY_P1 1.5
+    #define FXAA_QUALITY_P2 2.0
+    #define FXAA_QUALITY_P3 2.0
+    #define FXAA_QUALITY_P4 2.0
+    #define FXAA_QUALITY_P5 8.0
+#endif
+/*--------------------------------------------------------------------------*/
+#if (FXAA_QUALITY_PRESET == 24)
+    #define FXAA_QUALITY_PS 7
+    #define FXAA_QUALITY_P0 1.0
+    #define FXAA_QUALITY_P1 1.5
+    #define FXAA_QUALITY_P2 2.0
+    #define FXAA_QUALITY_P3 2.0
+    #define FXAA_QUALITY_P4 2.0
+    #define FXAA_QUALITY_P5 3.0
+    #define FXAA_QUALITY_P6 8.0
+#endif
+/*--------------------------------------------------------------------------*/
+#if (FXAA_QUALITY_PRESET == 25)
+    #define FXAA_QUALITY_PS 8
+    #define FXAA_QUALITY_P0 1.0
+    #define FXAA_QUALITY_P1 1.5
+    #define FXAA_QUALITY_P2 2.0
+    #define FXAA_QUALITY_P3 2.0
+    #define FXAA_QUALITY_P4 2.0
+    #define FXAA_QUALITY_P5 2.0
+    #define FXAA_QUALITY_P6 4.0
+    #define FXAA_QUALITY_P7 8.0
+#endif
+/*--------------------------------------------------------------------------*/
+#if (FXAA_QUALITY_PRESET == 26)
+    #define FXAA_QUALITY_PS 9
+    #define FXAA_QUALITY_P0 1.0
+    #define FXAA_QUALITY_P1 1.5
+    #define FXAA_QUALITY_P2 2.0
+    #define FXAA_QUALITY_P3 2.0
+    #define FXAA_QUALITY_P4 2.0
+    #define FXAA_QUALITY_P5 2.0
+    #define FXAA_QUALITY_P6 2.0
+    #define FXAA_QUALITY_P7 4.0
+    #define FXAA_QUALITY_P8 8.0
+#endif
+/*--------------------------------------------------------------------------*/
+#if (FXAA_QUALITY_PRESET == 27)
+    #define FXAA_QUALITY_PS 10
+    #define FXAA_QUALITY_P0 1.0
+    #define FXAA_QUALITY_P1 1.5
+    #define FXAA_QUALITY_P2 2.0
+    #define FXAA_QUALITY_P3 2.0
+    #define FXAA_QUALITY_P4 2.0
+    #define FXAA_QUALITY_P5 2.0
+    #define FXAA_QUALITY_P6 2.0
+    #define FXAA_QUALITY_P7 2.0
+    #define FXAA_QUALITY_P8 4.0
+    #define FXAA_QUALITY_P9 8.0
+#endif
+/*--------------------------------------------------------------------------*/
+#if (FXAA_QUALITY_PRESET == 28)
+    #define FXAA_QUALITY_PS 11
+    #define FXAA_QUALITY_P0 1.0
+    #define FXAA_QUALITY_P1 1.5
+    #define FXAA_QUALITY_P2 2.0
+    #define FXAA_QUALITY_P3 2.0
+    #define FXAA_QUALITY_P4 2.0
+    #define FXAA_QUALITY_P5 2.0
+    #define FXAA_QUALITY_P6 2.0
+    #define FXAA_QUALITY_P7 2.0
+    #define FXAA_QUALITY_P8 2.0
+    #define FXAA_QUALITY_P9 4.0
+    #define FXAA_QUALITY_P10 8.0
+#endif
+/*--------------------------------------------------------------------------*/
+#if (FXAA_QUALITY_PRESET == 29)
+    #define FXAA_QUALITY_PS 12
+    #define FXAA_QUALITY_P0 1.0
+    #define FXAA_QUALITY_P1 1.5
+    #define FXAA_QUALITY_P2 2.0
+    #define FXAA_QUALITY_P3 2.0
+    #define FXAA_QUALITY_P4 2.0
+    #define FXAA_QUALITY_P5 2.0
+    #define FXAA_QUALITY_P6 2.0
+    #define FXAA_QUALITY_P7 2.0
+    #define FXAA_QUALITY_P8 2.0
+    #define FXAA_QUALITY_P9 2.0
+    #define FXAA_QUALITY_P10 4.0
+    #define FXAA_QUALITY_P11 8.0
+#endif
+
+/*============================================================================
+                     FXAA QUALITY - EXTREME QUALITY
+============================================================================*/
+#if (FXAA_QUALITY_PRESET == 39)
+    #define FXAA_QUALITY_PS 12
+    #define FXAA_QUALITY_P0 1.0
+    #define FXAA_QUALITY_P1 1.0
+    #define FXAA_QUALITY_P2 1.0
+    #define FXAA_QUALITY_P3 1.0
+    #define FXAA_QUALITY_P4 1.0
+    #define FXAA_QUALITY_P5 1.5
+    #define FXAA_QUALITY_P6 2.0
+    #define FXAA_QUALITY_P7 2.0
+    #define FXAA_QUALITY_P8 2.0
+    #define FXAA_QUALITY_P9 2.0
+    #define FXAA_QUALITY_P10 4.0
+    #define FXAA_QUALITY_P11 8.0
+#endif
+
+
+
+/*============================================================================
+
+                                API PORTING
+
+============================================================================*/
+#if (FXAA_GLSL_120 == 1) || (FXAA_GLSL_130 == 1)
+    #define FxaaBool bool
+    #define FxaaDiscard discard
+    #define FxaaFloat float
+    #define FxaaFloat2 vec2
+    #define FxaaFloat3 vec3
+    #define FxaaFloat4 vec4
+    #define FxaaHalf float
+    #define FxaaHalf2 vec2
+    #define FxaaHalf3 vec3
+    #define FxaaHalf4 vec4
+    #define FxaaInt2 ivec2
+    #define FxaaSat(x) clamp(x, 0.0, 1.0)
+    #define FxaaTex sampler2D
+#else
+    #define FxaaBool bool
+    #define FxaaDiscard clip(-1)
+    #define FxaaFloat float
+    #define FxaaFloat2 float2
+    #define FxaaFloat3 float3
+    #define FxaaFloat4 float4
+    #define FxaaHalf half
+    #define FxaaHalf2 half2
+    #define FxaaHalf3 half3
+    #define FxaaHalf4 half4
+    #define FxaaSat(x) saturate(x)
+#endif
+/*--------------------------------------------------------------------------*/
+#if (FXAA_GLSL_120 == 1)
+    // Requires,
+    //  #version 120
+    // And at least,
+    //  #extension GL_EXT_gpu_shader4 : enable
+    //  (or set FXAA_FAST_PIXEL_OFFSET 1 to work like DX9)
+    #define FxaaTexTop(t, p) texture2DLod(t, p, 0.0)
+    #if (FXAA_FAST_PIXEL_OFFSET == 1)
+        #define FxaaTexOff(t, p, o, r) texture2DLodOffset(t, p, 0.0, o)
+    #else
+        #define FxaaTexOff(t, p, o, r) texture2DLod(t, p + (o * r), 0.0)
+    #endif
+    #if (FXAA_GATHER4_ALPHA == 1)
+        // use #extension GL_ARB_gpu_shader5 : enable
+        #define FxaaTexAlpha4(t, p) textureGather(t, p, 3)
+        #define FxaaTexOffAlpha4(t, p, o) textureGatherOffset(t, p, o, 3)
+        #define FxaaTexGreen4(t, p) textureGather(t, p, 1)
+        #define FxaaTexOffGreen4(t, p, o) textureGatherOffset(t, p, o, 1)
+    #endif
+#endif
+/*--------------------------------------------------------------------------*/
+#if (FXAA_GLSL_130 == 1)
+    // Requires "#version 130" or better
+    #define FxaaTexTop(t, p) textureLod(t, p, 0.0)
+    #define FxaaTexOff(t, p, o, r) textureLodOffset(t, p, 0.0, o)
+    #if (FXAA_GATHER4_ALPHA == 1)
+        // use #extension GL_ARB_gpu_shader5 : enable
+        #define FxaaTexAlpha4(t, p) textureGather(t, p, 3)
+        #define FxaaTexOffAlpha4(t, p, o) textureGatherOffset(t, p, o, 3)
+        #define FxaaTexGreen4(t, p) textureGather(t, p, 1)
+        #define FxaaTexOffGreen4(t, p, o) textureGatherOffset(t, p, o, 1)
+    #endif
+#endif
+/*--------------------------------------------------------------------------*/
+#if (FXAA_HLSL_3 == 1) || (FXAA_360 == 1) || (FXAA_PS3 == 1)
+    #define FxaaInt2 float2
+    #define FxaaTex sampler2D
+    #define FxaaTexTop(t, p) tex2Dlod(t, float4(p, 0.0, 0.0))
+    #define FxaaTexOff(t, p, o, r) tex2Dlod(t, float4(p + (o * r), 0, 0))
+#endif
+/*--------------------------------------------------------------------------*/
+#if (FXAA_HLSL_4 == 1)
+    #define FxaaInt2 int2
+    struct FxaaTex { SamplerState smpl; Texture2D tex; };
+    #define FxaaTexTop(t, p) t.tex.SampleLevel(t.smpl, p, 0.0)
+    #define FxaaTexOff(t, p, o, r) t.tex.SampleLevel(t.smpl, p, 0.0, o)
+#endif
+/*--------------------------------------------------------------------------*/
+#if (FXAA_HLSL_5 == 1)
+    #define FxaaInt2 int2
+    struct FxaaTex { SamplerState smpl; Texture2D tex; };
+    #define FxaaTexTop(t, p) t.tex.SampleLevel(t.smpl, p, 0.0)
+    #define FxaaTexOff(t, p, o, r) t.tex.SampleLevel(t.smpl, p, 0.0, o)
+    #define FxaaTexAlpha4(t, p) t.tex.GatherAlpha(t.smpl, p)
+    #define FxaaTexOffAlpha4(t, p, o) t.tex.GatherAlpha(t.smpl, p, o)
+    #define FxaaTexGreen4(t, p) t.tex.GatherGreen(t.smpl, p)
+    #define FxaaTexOffGreen4(t, p, o) t.tex.GatherGreen(t.smpl, p, o)
+#endif
+
+
+/*============================================================================
+                   GREEN AS LUMA OPTION SUPPORT FUNCTION
+============================================================================*/
+#if (FXAA_GREEN_AS_LUMA == 0)
+    FxaaFloat FxaaLuma(FxaaFloat4 rgba) { return rgba.w; }
+#else
+    FxaaFloat FxaaLuma(FxaaFloat4 rgba) { return rgba.y; }
+#endif    
+
+
+
+
+/*============================================================================
+
+                             FXAA3 QUALITY - PC
+
+============================================================================*/
+#if (FXAA_PC == 1)
+/*--------------------------------------------------------------------------*/
+FxaaFloat4 FxaaPixelShader(
+    //
+    // Use noperspective interpolation here (turn off perspective interpolation).
+    // {xy} = center of pixel
+    FxaaFloat2 pos,
+    //
+    // Used only for FXAA Console, and not used on the 360 version.
+    // Use noperspective interpolation here (turn off perspective interpolation).
+    // {xy__} = upper left of pixel
+    // {__zw} = lower right of pixel
+    FxaaFloat4 fxaaConsolePosPos,
+    //
+    // Input color texture.
+    // {rgb_} = color in linear or perceptual color space
+    // if (FXAA_GREEN_AS_LUMA == 0)
+    //     {___a} = luma in perceptual color space (not linear)
+    FxaaTex tex,
+    //
+    // Only used on the optimized 360 version of FXAA Console.
+    // For everything but 360, just use the same input here as for "tex".
+    // For 360, same texture, just alias with a 2nd sampler.
+    // This sampler needs to have an exponent bias of -1.
+    FxaaTex fxaaConsole360TexExpBiasNegOne,
+    //
+    // Only used on the optimized 360 version of FXAA Console.
+    // For everything but 360, just use the same input here as for "tex".
+    // For 360, same texture, just alias with a 3nd sampler.
+    // This sampler needs to have an exponent bias of -2.
+    FxaaTex fxaaConsole360TexExpBiasNegTwo,
+    //
+    // Only used on FXAA Quality.
+    // This must be from a constant/uniform.
+    // {x_} = 1.0/screenWidthInPixels
+    // {_y} = 1.0/screenHeightInPixels
+    FxaaFloat2 fxaaQualityRcpFrame,
+    //
+    // Only used on FXAA Console.
+    // This must be from a constant/uniform.
+    // This effects sub-pixel AA quality and inversely sharpness.
+    //   Where N ranges between,
+    //     N = 0.50 (default)
+    //     N = 0.33 (sharper)
+    // {x___} = -N/screenWidthInPixels  
+    // {_y__} = -N/screenHeightInPixels
+    // {__z_} =  N/screenWidthInPixels  
+    // {___w} =  N/screenHeightInPixels 
+    FxaaFloat4 fxaaConsoleRcpFrameOpt,
+    //
+    // Only used on FXAA Console.
+    // Not used on 360, but used on PS3 and PC.
+    // This must be from a constant/uniform.
+    // {x___} = -2.0/screenWidthInPixels  
+    // {_y__} = -2.0/screenHeightInPixels
+    // {__z_} =  2.0/screenWidthInPixels  
+    // {___w} =  2.0/screenHeightInPixels 
+    FxaaFloat4 fxaaConsoleRcpFrameOpt2,
+    //
+    // Only used on FXAA Console.
+    // Only used on 360 in place of fxaaConsoleRcpFrameOpt2.
+    // This must be from a constant/uniform.
+    // {x___} =  8.0/screenWidthInPixels  
+    // {_y__} =  8.0/screenHeightInPixels
+    // {__z_} = -4.0/screenWidthInPixels  
+    // {___w} = -4.0/screenHeightInPixels 
+    FxaaFloat4 fxaaConsole360RcpFrameOpt2,
+    //
+    // Only used on FXAA Quality.
+    // This used to be the FXAA_QUALITY_SUBPIX define.
+    // It is here now to allow easier tuning.
+    // Choose the amount of sub-pixel aliasing removal.
+    // This can effect sharpness.
+    //   1.00 - upper limit (softer)
+    //   0.75 - default amount of filtering
+    //   0.50 - lower limit (sharper, less sub-pixel aliasing removal)
+    //   0.25 - almost off
+    //   0.00 - completely off
+    FxaaFloat fxaaQualitySubpix,
+    //
+    // Only used on FXAA Quality.
+    // This used to be the FXAA_QUALITY_EDGE_THRESHOLD define.
+    // It is here now to allow easier tuning.
+    // The minimum amount of local contrast required to apply algorithm.
+    //   0.333 - too little (faster)
+    //   0.250 - low quality
+    //   0.166 - default
+    //   0.125 - high quality 
+    //   0.063 - overkill (slower)
+    FxaaFloat fxaaQualityEdgeThreshold,
+    //
+    // Only used on FXAA Quality.
+    // This used to be the FXAA_QUALITY_EDGE_THRESHOLD_MIN define.
+    // It is here now to allow easier tuning.
+    // Trims the algorithm from processing darks.
+    //   0.0833 - upper limit (default, the start of visible unfiltered edges)
+    //   0.0625 - high quality (faster)
+    //   0.0312 - visible limit (slower)
+    // Special notes when using FXAA_GREEN_AS_LUMA,
+    //   Likely want to set this to zero.
+    //   As colors that are mostly not-green
+    //   will appear very dark in the green channel!
+    //   Tune by looking at mostly non-green content,
+    //   then start at zero and increase until aliasing is a problem.
+    FxaaFloat fxaaQualityEdgeThresholdMin,
+    // 
+    // Only used on FXAA Console.
+    // This used to be the FXAA_CONSOLE_EDGE_SHARPNESS define.
+    // It is here now to allow easier tuning.
+    // This does not effect PS3, as this needs to be compiled in.
+    //   Use FXAA_CONSOLE_PS3_EDGE_SHARPNESS for PS3.
+    //   Due to the PS3 being ALU bound,
+    //   there are only three safe values here: 2 and 4 and 8.
+    //   These options use the shaders ability to a free *|/ by 2|4|8.
+    // For all other platforms can be a non-power of two.
+    //   8.0 is sharper (default!!!)
+    //   4.0 is softer
+    //   2.0 is really soft (good only for vector graphics inputs)
+    FxaaFloat fxaaConsoleEdgeSharpness,
+    //
+    // Only used on FXAA Console.
+    // This used to be the FXAA_CONSOLE_EDGE_THRESHOLD define.
+    // It is here now to allow easier tuning.
+    // This does not effect PS3, as this needs to be compiled in.
+    //   Use FXAA_CONSOLE_PS3_EDGE_THRESHOLD for PS3.
+    //   Due to the PS3 being ALU bound,
+    //   there are only two safe values here: 1/4 and 1/8.
+    //   These options use the shaders ability to a free *|/ by 2|4|8.
+    // The console setting has a different mapping than the quality setting.
+    // Other platforms can use other values.
+    //   0.125 leaves less aliasing, but is softer (default!!!)
+    //   0.25 leaves more aliasing, and is sharper
+    FxaaFloat fxaaConsoleEdgeThreshold,
+    //
+    // Only used on FXAA Console.
+    // This used to be the FXAA_CONSOLE_EDGE_THRESHOLD_MIN define.
+    // It is here now to allow easier tuning.
+    // Trims the algorithm from processing darks.
+    // The console setting has a different mapping than the quality setting.
+    // This only applies when FXAA_EARLY_EXIT is 1.
+    // This does not apply to PS3, 
+    // PS3 was simplified to avoid more shader instructions.
+    //   0.06 - faster but more aliasing in darks
+    //   0.05 - default
+    //   0.04 - slower and less aliasing in darks
+    // Special notes when using FXAA_GREEN_AS_LUMA,
+    //   Likely want to set this to zero.
+    //   As colors that are mostly not-green
+    //   will appear very dark in the green channel!
+    //   Tune by looking at mostly non-green content,
+    //   then start at zero and increase until aliasing is a problem.
+    FxaaFloat fxaaConsoleEdgeThresholdMin,
+    //    
+    // Extra constants for 360 FXAA Console only.
+    // Use zeros or anything else for other platforms.
+    // These must be in physical constant registers and NOT immedates.
+    // Immedates will result in compiler un-optimizing.
+    // {xyzw} = float4(1.0, -1.0, 0.25, -0.25)
+    FxaaFloat4 fxaaConsole360ConstDir
+) {
+/*--------------------------------------------------------------------------*/
+    FxaaFloat2 posM;
+    posM.x = pos.x;
+    posM.y = pos.y;
+    #if (FXAA_GATHER4_ALPHA == 1)
+        #if (FXAA_DISCARD == 0)
+            FxaaFloat4 rgbyM = FxaaTexTop(tex, posM);
+            #if (FXAA_GREEN_AS_LUMA == 0)
+                #define lumaM rgbyM.w
+            #else
+                #define lumaM rgbyM.y
+            #endif
+        #endif
+        #if (FXAA_GREEN_AS_LUMA == 0)
+            FxaaFloat4 luma4A = FxaaTexAlpha4(tex, posM);
+            FxaaFloat4 luma4B = FxaaTexOffAlpha4(tex, posM, FxaaInt2(-1, -1));
+        #else
+            FxaaFloat4 luma4A = FxaaTexGreen4(tex, posM);
+            FxaaFloat4 luma4B = FxaaTexOffGreen4(tex, posM, FxaaInt2(-1, -1));
+        #endif
+        #if (FXAA_DISCARD == 1)
+            #define lumaM luma4A.w
+        #endif
+        #define lumaE luma4A.z
+        #define lumaS luma4A.x
+        #define lumaSE luma4A.y
+        #define lumaNW luma4B.w
+        #define lumaN luma4B.z
+        #define lumaW luma4B.x
+    #else
+        FxaaFloat4 rgbyM = FxaaTexTop(tex, posM);
+        #if (FXAA_GREEN_AS_LUMA == 0)
+            #define lumaM rgbyM.w
+        #else
+            #define lumaM rgbyM.y
+        #endif
+        FxaaFloat lumaS = FxaaLuma(FxaaTexOff(tex, posM, FxaaInt2( 0, 1), fxaaQualityRcpFrame.xy));
+        FxaaFloat lumaE = FxaaLuma(FxaaTexOff(tex, posM, FxaaInt2( 1, 0), fxaaQualityRcpFrame.xy));
+        FxaaFloat lumaN = FxaaLuma(FxaaTexOff(tex, posM, FxaaInt2( 0,-1), fxaaQualityRcpFrame.xy));
+        FxaaFloat lumaW = FxaaLuma(FxaaTexOff(tex, posM, FxaaInt2(-1, 0), fxaaQualityRcpFrame.xy));
+    #endif
+/*--------------------------------------------------------------------------*/
+    FxaaFloat maxSM = max(lumaS, lumaM);
+    FxaaFloat minSM = min(lumaS, lumaM);
+    FxaaFloat maxESM = max(lumaE, maxSM);
+    FxaaFloat minESM = min(lumaE, minSM);
+    FxaaFloat maxWN = max(lumaN, lumaW);
+    FxaaFloat minWN = min(lumaN, lumaW);
+    FxaaFloat rangeMax = max(maxWN, maxESM);
+    FxaaFloat rangeMin = min(minWN, minESM);
+    FxaaFloat rangeMaxScaled = rangeMax * fxaaQualityEdgeThreshold;
+    FxaaFloat range = rangeMax - rangeMin;
+    FxaaFloat rangeMaxClamped = max(fxaaQualityEdgeThresholdMin, rangeMaxScaled);
+    FxaaBool earlyExit = range < rangeMaxClamped;
+/*--------------------------------------------------------------------------*/
+    if(earlyExit)
+        #if (FXAA_DISCARD == 1)
+            FxaaDiscard;
+        #else
+            return rgbyM;
+        #endif
+/*--------------------------------------------------------------------------*/
+    #if (FXAA_GATHER4_ALPHA == 0)
+        FxaaFloat lumaNW = FxaaLuma(FxaaTexOff(tex, posM, FxaaInt2(-1,-1), fxaaQualityRcpFrame.xy));
+        FxaaFloat lumaSE = FxaaLuma(FxaaTexOff(tex, posM, FxaaInt2( 1, 1), fxaaQualityRcpFrame.xy));
+        FxaaFloat lumaNE = FxaaLuma(FxaaTexOff(tex, posM, FxaaInt2( 1,-1), fxaaQualityRcpFrame.xy));
+        FxaaFloat lumaSW = FxaaLuma(FxaaTexOff(tex, posM, FxaaInt2(-1, 1), fxaaQualityRcpFrame.xy));
+    #else
+        FxaaFloat lumaNE = FxaaLuma(FxaaTexOff(tex, posM, FxaaInt2(1, -1), fxaaQualityRcpFrame.xy));
+        FxaaFloat lumaSW = FxaaLuma(FxaaTexOff(tex, posM, FxaaInt2(-1, 1), fxaaQualityRcpFrame.xy));
+    #endif
+/*--------------------------------------------------------------------------*/
+    FxaaFloat lumaNS = lumaN + lumaS;
+    FxaaFloat lumaWE = lumaW + lumaE;
+    FxaaFloat subpixRcpRange = 1.0/range;
+    FxaaFloat subpixNSWE = lumaNS + lumaWE;
+    FxaaFloat edgeHorz1 = (-2.0 * lumaM) + lumaNS;
+    FxaaFloat edgeVert1 = (-2.0 * lumaM) + lumaWE;
+/*--------------------------------------------------------------------------*/
+    FxaaFloat lumaNESE = lumaNE + lumaSE;
+    FxaaFloat lumaNWNE = lumaNW + lumaNE;
+    FxaaFloat edgeHorz2 = (-2.0 * lumaE) + lumaNESE;
+    FxaaFloat edgeVert2 = (-2.0 * lumaN) + lumaNWNE;
+/*--------------------------------------------------------------------------*/
+    FxaaFloat lumaNWSW = lumaNW + lumaSW;
+    FxaaFloat lumaSWSE = lumaSW + lumaSE;
+    FxaaFloat edgeHorz4 = (abs(edgeHorz1) * 2.0) + abs(edgeHorz2);
+    FxaaFloat edgeVert4 = (abs(edgeVert1) * 2.0) + abs(edgeVert2);
+    FxaaFloat edgeHorz3 = (-2.0 * lumaW) + lumaNWSW;
+    FxaaFloat edgeVert3 = (-2.0 * lumaS) + lumaSWSE;
+    FxaaFloat edgeHorz = abs(edgeHorz3) + edgeHorz4;
+    FxaaFloat edgeVert = abs(edgeVert3) + edgeVert4;
+/*--------------------------------------------------------------------------*/
+    FxaaFloat subpixNWSWNESE = lumaNWSW + lumaNESE;
+    FxaaFloat lengthSign = fxaaQualityRcpFrame.x;
+    FxaaBool horzSpan = edgeHorz >= edgeVert;
+    FxaaFloat subpixA = subpixNSWE * 2.0 + subpixNWSWNESE;
+/*--------------------------------------------------------------------------*/
+    if(!horzSpan) lumaN = lumaW;
+    if(!horzSpan) lumaS = lumaE;
+    if(horzSpan) lengthSign = fxaaQualityRcpFrame.y;
+    FxaaFloat subpixB = (subpixA * (1.0/12.0)) - lumaM;
+/*--------------------------------------------------------------------------*/
+    FxaaFloat gradientN = lumaN - lumaM;
+    FxaaFloat gradientS = lumaS - lumaM;
+    FxaaFloat lumaNN = lumaN + lumaM;
+    FxaaFloat lumaSS = lumaS + lumaM;
+    FxaaBool pairN = abs(gradientN) >= abs(gradientS);
+    FxaaFloat gradient = max(abs(gradientN), abs(gradientS));
+    if(pairN) lengthSign = -lengthSign;
+    FxaaFloat subpixC = FxaaSat(abs(subpixB) * subpixRcpRange);
+/*--------------------------------------------------------------------------*/
+    FxaaFloat2 posB;
+    posB.x = posM.x;
+    posB.y = posM.y;
+    FxaaFloat2 offNP;
+    offNP.x = (!horzSpan) ? 0.0 : fxaaQualityRcpFrame.x;
+    offNP.y = ( horzSpan) ? 0.0 : fxaaQualityRcpFrame.y;
+    if(!horzSpan) posB.x += lengthSign * 0.5;
+    if( horzSpan) posB.y += lengthSign * 0.5;
+/*--------------------------------------------------------------------------*/
+    FxaaFloat2 posN;
+    posN.x = posB.x - offNP.x * FXAA_QUALITY_P0;
+    posN.y = posB.y - offNP.y * FXAA_QUALITY_P0;
+    FxaaFloat2 posP;
+    posP.x = posB.x + offNP.x * FXAA_QUALITY_P0;
+    posP.y = posB.y + offNP.y * FXAA_QUALITY_P0;
+    FxaaFloat subpixD = ((-2.0)*subpixC) + 3.0;
+    FxaaFloat lumaEndN = FxaaLuma(FxaaTexTop(tex, posN));
+    FxaaFloat subpixE = subpixC * subpixC;
+    FxaaFloat lumaEndP = FxaaLuma(FxaaTexTop(tex, posP));
+/*--------------------------------------------------------------------------*/
+    if(!pairN) lumaNN = lumaSS;
+    FxaaFloat gradientScaled = gradient * 1.0/4.0;
+    FxaaFloat lumaMM = lumaM - lumaNN * 0.5;
+    FxaaFloat subpixF = subpixD * subpixE;
+    FxaaBool lumaMLTZero = lumaMM < 0.0;
+/*--------------------------------------------------------------------------*/
+    lumaEndN -= lumaNN * 0.5;
+    lumaEndP -= lumaNN * 0.5;
+    FxaaBool doneN = abs(lumaEndN) >= gradientScaled;
+    FxaaBool doneP = abs(lumaEndP) >= gradientScaled;
+    if(!doneN) posN.x -= offNP.x * FXAA_QUALITY_P1;
+    if(!doneN) posN.y -= offNP.y * FXAA_QUALITY_P1;
+    FxaaBool doneNP = (!doneN) || (!doneP);
+    if(!doneP) posP.x += offNP.x * FXAA_QUALITY_P1;
+    if(!doneP) posP.y += offNP.y * FXAA_QUALITY_P1;
+/*--------------------------------------------------------------------------*/
+    if(doneNP) {
+        if(!doneN) lumaEndN = FxaaLuma(FxaaTexTop(tex, posN.xy));
+        if(!doneP) lumaEndP = FxaaLuma(FxaaTexTop(tex, posP.xy));
+        if(!doneN) lumaEndN = lumaEndN - lumaNN * 0.5;
+        if(!doneP) lumaEndP = lumaEndP - lumaNN * 0.5;
+        doneN = abs(lumaEndN) >= gradientScaled;
+        doneP = abs(lumaEndP) >= gradientScaled;
+        if(!doneN) posN.x -= offNP.x * FXAA_QUALITY_P2;
+        if(!doneN) posN.y -= offNP.y * FXAA_QUALITY_P2;
+        doneNP = (!doneN) || (!doneP);
+        if(!doneP) posP.x += offNP.x * FXAA_QUALITY_P2;
+        if(!doneP) posP.y += offNP.y * FXAA_QUALITY_P2;
+/*--------------------------------------------------------------------------*/
+        #if (FXAA_QUALITY_PS > 3)
+        if(doneNP) {
+            if(!doneN) lumaEndN = FxaaLuma(FxaaTexTop(tex, posN.xy));
+            if(!doneP) lumaEndP = FxaaLuma(FxaaTexTop(tex, posP.xy));
+            if(!doneN) lumaEndN = lumaEndN - lumaNN * 0.5;
+            if(!doneP) lumaEndP = lumaEndP - lumaNN * 0.5;
+            doneN = abs(lumaEndN) >= gradientScaled;
+            doneP = abs(lumaEndP) >= gradientScaled;
+            if(!doneN) posN.x -= offNP.x * FXAA_QUALITY_P3;
+            if(!doneN) posN.y -= offNP.y * FXAA_QUALITY_P3;
+            doneNP = (!doneN) || (!doneP);
+            if(!doneP) posP.x += offNP.x * FXAA_QUALITY_P3;
+            if(!doneP) posP.y += offNP.y * FXAA_QUALITY_P3;
+/*--------------------------------------------------------------------------*/
+            #if (FXAA_QUALITY_PS > 4)
+            if(doneNP) {
+                if(!doneN) lumaEndN = FxaaLuma(FxaaTexTop(tex, posN.xy));
+                if(!doneP) lumaEndP = FxaaLuma(FxaaTexTop(tex, posP.xy));
+                if(!doneN) lumaEndN = lumaEndN - lumaNN * 0.5;
+                if(!doneP) lumaEndP = lumaEndP - lumaNN * 0.5;
+                doneN = abs(lumaEndN) >= gradientScaled;
+                doneP = abs(lumaEndP) >= gradientScaled;
+                if(!doneN) posN.x -= offNP.x * FXAA_QUALITY_P4;
+                if(!doneN) posN.y -= offNP.y * FXAA_QUALITY_P4;
+                doneNP = (!doneN) || (!doneP);
+                if(!doneP) posP.x += offNP.x * FXAA_QUALITY_P4;
+                if(!doneP) posP.y += offNP.y * FXAA_QUALITY_P4;
+/*--------------------------------------------------------------------------*/
+                #if (FXAA_QUALITY_PS > 5)
+                if(doneNP) {
+                    if(!doneN) lumaEndN = FxaaLuma(FxaaTexTop(tex, posN.xy));
+                    if(!doneP) lumaEndP = FxaaLuma(FxaaTexTop(tex, posP.xy));
+                    if(!doneN) lumaEndN = lumaEndN - lumaNN * 0.5;
+                    if(!doneP) lumaEndP = lumaEndP - lumaNN * 0.5;
+                    doneN = abs(lumaEndN) >= gradientScaled;
+                    doneP = abs(lumaEndP) >= gradientScaled;
+                    if(!doneN) posN.x -= offNP.x * FXAA_QUALITY_P5;
+                    if(!doneN) posN.y -= offNP.y * FXAA_QUALITY_P5;
+                    doneNP = (!doneN) || (!doneP);
+                    if(!doneP) posP.x += offNP.x * FXAA_QUALITY_P5;
+                    if(!doneP) posP.y += offNP.y * FXAA_QUALITY_P5;
+/*--------------------------------------------------------------------------*/
+                    #if (FXAA_QUALITY_PS > 6)
+                    if(doneNP) {
+                        if(!doneN) lumaEndN = FxaaLuma(FxaaTexTop(tex, posN.xy));
+                        if(!doneP) lumaEndP = FxaaLuma(FxaaTexTop(tex, posP.xy));
+                        if(!doneN) lumaEndN = lumaEndN - lumaNN * 0.5;
+                        if(!doneP) lumaEndP = lumaEndP - lumaNN * 0.5;
+                        doneN = abs(lumaEndN) >= gradientScaled;
+                        doneP = abs(lumaEndP) >= gradientScaled;
+                        if(!doneN) posN.x -= offNP.x * FXAA_QUALITY_P6;
+                        if(!doneN) posN.y -= offNP.y * FXAA_QUALITY_P6;
+                        doneNP = (!doneN) || (!doneP);
+                        if(!doneP) posP.x += offNP.x * FXAA_QUALITY_P6;
+                        if(!doneP) posP.y += offNP.y * FXAA_QUALITY_P6;
+/*--------------------------------------------------------------------------*/
+                        #if (FXAA_QUALITY_PS > 7)
+                        if(doneNP) {
+                            if(!doneN) lumaEndN = FxaaLuma(FxaaTexTop(tex, posN.xy));
+                            if(!doneP) lumaEndP = FxaaLuma(FxaaTexTop(tex, posP.xy));
+                            if(!doneN) lumaEndN = lumaEndN - lumaNN * 0.5;
+                            if(!doneP) lumaEndP = lumaEndP - lumaNN * 0.5;
+                            doneN = abs(lumaEndN) >= gradientScaled;
+                            doneP = abs(lumaEndP) >= gradientScaled;
+                            if(!doneN) posN.x -= offNP.x * FXAA_QUALITY_P7;
+                            if(!doneN) posN.y -= offNP.y * FXAA_QUALITY_P7;
+                            doneNP = (!doneN) || (!doneP);
+                            if(!doneP) posP.x += offNP.x * FXAA_QUALITY_P7;
+                            if(!doneP) posP.y += offNP.y * FXAA_QUALITY_P7;
+/*--------------------------------------------------------------------------*/
+    #if (FXAA_QUALITY_PS > 8)
+    if(doneNP) {
+        if(!doneN) lumaEndN = FxaaLuma(FxaaTexTop(tex, posN.xy));
+        if(!doneP) lumaEndP = FxaaLuma(FxaaTexTop(tex, posP.xy));
+        if(!doneN) lumaEndN = lumaEndN - lumaNN * 0.5;
+        if(!doneP) lumaEndP = lumaEndP - lumaNN * 0.5;
+        doneN = abs(lumaEndN) >= gradientScaled;
+        doneP = abs(lumaEndP) >= gradientScaled;
+        if(!doneN) posN.x -= offNP.x * FXAA_QUALITY_P8;
+        if(!doneN) posN.y -= offNP.y * FXAA_QUALITY_P8;
+        doneNP = (!doneN) || (!doneP);
+        if(!doneP) posP.x += offNP.x * FXAA_QUALITY_P8;
+        if(!doneP) posP.y += offNP.y * FXAA_QUALITY_P8;
+/*--------------------------------------------------------------------------*/
+        #if (FXAA_QUALITY_PS > 9)
+        if(doneNP) {
+            if(!doneN) lumaEndN = FxaaLuma(FxaaTexTop(tex, posN.xy));
+            if(!doneP) lumaEndP = FxaaLuma(FxaaTexTop(tex, posP.xy));
+            if(!doneN) lumaEndN = lumaEndN - lumaNN * 0.5;
+            if(!doneP) lumaEndP = lumaEndP - lumaNN * 0.5;
+            doneN = abs(lumaEndN) >= gradientScaled;
+            doneP = abs(lumaEndP) >= gradientScaled;
+            if(!doneN) posN.x -= offNP.x * FXAA_QUALITY_P9;
+            if(!doneN) posN.y -= offNP.y * FXAA_QUALITY_P9;
+            doneNP = (!doneN) || (!doneP);
+            if(!doneP) posP.x += offNP.x * FXAA_QUALITY_P9;
+            if(!doneP) posP.y += offNP.y * FXAA_QUALITY_P9;
+/*--------------------------------------------------------------------------*/
+            #if (FXAA_QUALITY_PS > 10)
+            if(doneNP) {
+                if(!doneN) lumaEndN = FxaaLuma(FxaaTexTop(tex, posN.xy));
+                if(!doneP) lumaEndP = FxaaLuma(FxaaTexTop(tex, posP.xy));
+                if(!doneN) lumaEndN = lumaEndN - lumaNN * 0.5;
+                if(!doneP) lumaEndP = lumaEndP - lumaNN * 0.5;
+                doneN = abs(lumaEndN) >= gradientScaled;
+                doneP = abs(lumaEndP) >= gradientScaled;
+                if(!doneN) posN.x -= offNP.x * FXAA_QUALITY_P10;
+                if(!doneN) posN.y -= offNP.y * FXAA_QUALITY_P10;
+                doneNP = (!doneN) || (!doneP);
+                if(!doneP) posP.x += offNP.x * FXAA_QUALITY_P10;
+                if(!doneP) posP.y += offNP.y * FXAA_QUALITY_P10;
+/*--------------------------------------------------------------------------*/
+                #if (FXAA_QUALITY_PS > 11)
+                if(doneNP) {
+                    if(!doneN) lumaEndN = FxaaLuma(FxaaTexTop(tex, posN.xy));
+                    if(!doneP) lumaEndP = FxaaLuma(FxaaTexTop(tex, posP.xy));
+                    if(!doneN) lumaEndN = lumaEndN - lumaNN * 0.5;
+                    if(!doneP) lumaEndP = lumaEndP - lumaNN * 0.5;
+                    doneN = abs(lumaEndN) >= gradientScaled;
+                    doneP = abs(lumaEndP) >= gradientScaled;
+                    if(!doneN) posN.x -= offNP.x * FXAA_QUALITY_P11;
+                    if(!doneN) posN.y -= offNP.y * FXAA_QUALITY_P11;
+                    doneNP = (!doneN) || (!doneP);
+                    if(!doneP) posP.x += offNP.x * FXAA_QUALITY_P11;
+                    if(!doneP) posP.y += offNP.y * FXAA_QUALITY_P11;
+/*--------------------------------------------------------------------------*/
+                    #if (FXAA_QUALITY_PS > 12)
+                    if(doneNP) {
+                        if(!doneN) lumaEndN = FxaaLuma(FxaaTexTop(tex, posN.xy));
+                        if(!doneP) lumaEndP = FxaaLuma(FxaaTexTop(tex, posP.xy));
+                        if(!doneN) lumaEndN = lumaEndN - lumaNN * 0.5;
+                        if(!doneP) lumaEndP = lumaEndP - lumaNN * 0.5;
+                        doneN = abs(lumaEndN) >= gradientScaled;
+                        doneP = abs(lumaEndP) >= gradientScaled;
+                        if(!doneN) posN.x -= offNP.x * FXAA_QUALITY_P12;
+                        if(!doneN) posN.y -= offNP.y * FXAA_QUALITY_P12;
+                        doneNP = (!doneN) || (!doneP);
+                        if(!doneP) posP.x += offNP.x * FXAA_QUALITY_P12;
+                        if(!doneP) posP.y += offNP.y * FXAA_QUALITY_P12;
+/*--------------------------------------------------------------------------*/
+                    }
+                    #endif
+/*--------------------------------------------------------------------------*/
+                }
+                #endif
+/*--------------------------------------------------------------------------*/
+            }
+            #endif
+/*--------------------------------------------------------------------------*/
+        }
+        #endif
+/*--------------------------------------------------------------------------*/
+    }
+    #endif
+/*--------------------------------------------------------------------------*/
+                        }
+                        #endif
+/*--------------------------------------------------------------------------*/
+                    }
+                    #endif
+/*--------------------------------------------------------------------------*/
+                }
+                #endif
+/*--------------------------------------------------------------------------*/
+            }
+            #endif
+/*--------------------------------------------------------------------------*/
+        }
+        #endif
+/*--------------------------------------------------------------------------*/
+    }
+/*--------------------------------------------------------------------------*/
+    FxaaFloat dstN = posM.x - posN.x;
+    FxaaFloat dstP = posP.x - posM.x;
+    if(!horzSpan) dstN = posM.y - posN.y;
+    if(!horzSpan) dstP = posP.y - posM.y;
+/*--------------------------------------------------------------------------*/
+    FxaaBool goodSpanN = (lumaEndN < 0.0) != lumaMLTZero;
+    FxaaFloat spanLength = (dstP + dstN);
+    FxaaBool goodSpanP = (lumaEndP < 0.0) != lumaMLTZero;
+    FxaaFloat spanLengthRcp = 1.0/spanLength;
+/*--------------------------------------------------------------------------*/
+    FxaaBool directionN = dstN < dstP;
+    FxaaFloat dst = min(dstN, dstP);
+    FxaaBool goodSpan = directionN ? goodSpanN : goodSpanP;
+    FxaaFloat subpixG = subpixF * subpixF;
+    FxaaFloat pixelOffset = (dst * (-spanLengthRcp)) + 0.5;
+    FxaaFloat subpixH = subpixG * fxaaQualitySubpix;
+/*--------------------------------------------------------------------------*/
+    FxaaFloat pixelOffsetGood = goodSpan ? pixelOffset : 0.0;
+    FxaaFloat pixelOffsetSubpix = max(pixelOffsetGood, subpixH);
+    if(!horzSpan) posM.x += pixelOffsetSubpix * lengthSign;
+    if( horzSpan) posM.y += pixelOffsetSubpix * lengthSign;
+    #if (FXAA_DISCARD == 1)
+        return FxaaTexTop(tex, posM);
+    #else
+        return FxaaFloat4(FxaaTexTop(tex, posM).xyz, lumaM);
+    #endif
+}
+/*==========================================================================*/
+#endif
+
+
+
+
+/*============================================================================
+
+                         FXAA3 CONSOLE - PC VERSION
+                         
+------------------------------------------------------------------------------
+Instead of using this on PC, I'd suggest just using FXAA Quality with
+    #define FXAA_QUALITY_PRESET 10
+Or 
+    #define FXAA_QUALITY_PRESET 20
+Either are higher qualilty and almost as fast as this on modern PC GPUs.
+============================================================================*/
+#if (FXAA_PC_CONSOLE == 1)
+/*--------------------------------------------------------------------------*/
+FxaaFloat4 FxaaPixelShader(
+    // See FXAA Quality FxaaPixelShader() source for docs on Inputs!
+    FxaaFloat2 pos,
+    FxaaFloat4 fxaaConsolePosPos,
+    FxaaTex tex,
+    FxaaTex fxaaConsole360TexExpBiasNegOne,
+    FxaaTex fxaaConsole360TexExpBiasNegTwo,
+    FxaaFloat2 fxaaQualityRcpFrame,
+    FxaaFloat4 fxaaConsoleRcpFrameOpt,
+    FxaaFloat4 fxaaConsoleRcpFrameOpt2,
+    FxaaFloat4 fxaaConsole360RcpFrameOpt2,
+    FxaaFloat fxaaQualitySubpix,
+    FxaaFloat fxaaQualityEdgeThreshold,
+    FxaaFloat fxaaQualityEdgeThresholdMin,
+    FxaaFloat fxaaConsoleEdgeSharpness,
+    FxaaFloat fxaaConsoleEdgeThreshold,
+    FxaaFloat fxaaConsoleEdgeThresholdMin,
+    FxaaFloat4 fxaaConsole360ConstDir
+) {
+/*--------------------------------------------------------------------------*/
+    FxaaFloat lumaNw = FxaaLuma(FxaaTexTop(tex, fxaaConsolePosPos.xy));
+    FxaaFloat lumaSw = FxaaLuma(FxaaTexTop(tex, fxaaConsolePosPos.xw));
+    FxaaFloat lumaNe = FxaaLuma(FxaaTexTop(tex, fxaaConsolePosPos.zy));
+    FxaaFloat lumaSe = FxaaLuma(FxaaTexTop(tex, fxaaConsolePosPos.zw));
+/*--------------------------------------------------------------------------*/
+    FxaaFloat4 rgbyM = FxaaTexTop(tex, pos.xy);
+    #if (FXAA_GREEN_AS_LUMA == 0)
+        FxaaFloat lumaM = rgbyM.w;
+    #else
+        FxaaFloat lumaM = rgbyM.y;
+    #endif
+/*--------------------------------------------------------------------------*/
+    FxaaFloat lumaMaxNwSw = max(lumaNw, lumaSw);
+    lumaNe += 1.0/384.0;
+    FxaaFloat lumaMinNwSw = min(lumaNw, lumaSw);
+/*--------------------------------------------------------------------------*/
+    FxaaFloat lumaMaxNeSe = max(lumaNe, lumaSe);
+    FxaaFloat lumaMinNeSe = min(lumaNe, lumaSe);
+/*--------------------------------------------------------------------------*/
+    FxaaFloat lumaMax = max(lumaMaxNeSe, lumaMaxNwSw);
+    FxaaFloat lumaMin = min(lumaMinNeSe, lumaMinNwSw);
+/*--------------------------------------------------------------------------*/
+    FxaaFloat lumaMaxScaled = lumaMax * fxaaConsoleEdgeThreshold;
+/*--------------------------------------------------------------------------*/
+    FxaaFloat lumaMinM = min(lumaMin, lumaM);
+    FxaaFloat lumaMaxScaledClamped = max(fxaaConsoleEdgeThresholdMin, lumaMaxScaled);
+    FxaaFloat lumaMaxM = max(lumaMax, lumaM);
+    FxaaFloat dirSwMinusNe = lumaSw - lumaNe;
+    FxaaFloat lumaMaxSubMinM = lumaMaxM - lumaMinM;
+    FxaaFloat dirSeMinusNw = lumaSe - lumaNw;
+    if(lumaMaxSubMinM < lumaMaxScaledClamped) return rgbyM;
+/*--------------------------------------------------------------------------*/
+    FxaaFloat2 dir;
+    dir.x = dirSwMinusNe + dirSeMinusNw;
+    dir.y = dirSwMinusNe - dirSeMinusNw;
+/*--------------------------------------------------------------------------*/
+    FxaaFloat2 dir1 = normalize(dir.xy);
+    FxaaFloat4 rgbyN1 = FxaaTexTop(tex, pos.xy - dir1 * fxaaConsoleRcpFrameOpt.zw);
+    FxaaFloat4 rgbyP1 = FxaaTexTop(tex, pos.xy + dir1 * fxaaConsoleRcpFrameOpt.zw);
+/*--------------------------------------------------------------------------*/
+    FxaaFloat dirAbsMinTimesC = min(abs(dir1.x), abs(dir1.y)) * fxaaConsoleEdgeSharpness;
+    FxaaFloat2 dir2 = clamp(dir1.xy / dirAbsMinTimesC, -2.0, 2.0);
+/*--------------------------------------------------------------------------*/
+    FxaaFloat4 rgbyN2 = FxaaTexTop(tex, pos.xy - dir2 * fxaaConsoleRcpFrameOpt2.zw);
+    FxaaFloat4 rgbyP2 = FxaaTexTop(tex, pos.xy + dir2 * fxaaConsoleRcpFrameOpt2.zw);
+/*--------------------------------------------------------------------------*/
+    FxaaFloat4 rgbyA = rgbyN1 + rgbyP1;
+    FxaaFloat4 rgbyB = ((rgbyN2 + rgbyP2) * 0.25) + (rgbyA * 0.25);
+/*--------------------------------------------------------------------------*/
+    #if (FXAA_GREEN_AS_LUMA == 0)
+        FxaaBool twoTap = (rgbyB.w < lumaMin) || (rgbyB.w > lumaMax);
+    #else
+        FxaaBool twoTap = (rgbyB.y < lumaMin) || (rgbyB.y > lumaMax);
+    #endif
+    if(twoTap) rgbyB.xyz = rgbyA.xyz * 0.5;
+    return rgbyB; }
+/*==========================================================================*/
+#endif
+
+
+
+/*============================================================================
+
+                      FXAA3 CONSOLE - 360 PIXEL SHADER 
+
+------------------------------------------------------------------------------
+This optimized version thanks to suggestions from Andy Luedke.
+Should be fully tex bound in all cases.
+As of the FXAA 3.11 release, I have still not tested this code,
+however I fixed a bug which was in both FXAA 3.9 and FXAA 3.10.
+And note this is replacing the old unoptimized version.
+If it does not work, please let me know so I can fix it.
+============================================================================*/
+#if (FXAA_360 == 1)
+/*--------------------------------------------------------------------------*/
+[reduceTempRegUsage(4)]
+float4 FxaaPixelShader(
+    // See FXAA Quality FxaaPixelShader() source for docs on Inputs!
+    FxaaFloat2 pos,
+    FxaaFloat4 fxaaConsolePosPos,
+    FxaaTex tex,
+    FxaaTex fxaaConsole360TexExpBiasNegOne,
+    FxaaTex fxaaConsole360TexExpBiasNegTwo,
+    FxaaFloat2 fxaaQualityRcpFrame,
+    FxaaFloat4 fxaaConsoleRcpFrameOpt,
+    FxaaFloat4 fxaaConsoleRcpFrameOpt2,
+    FxaaFloat4 fxaaConsole360RcpFrameOpt2,
+    FxaaFloat fxaaQualitySubpix,
+    FxaaFloat fxaaQualityEdgeThreshold,
+    FxaaFloat fxaaQualityEdgeThresholdMin,
+    FxaaFloat fxaaConsoleEdgeSharpness,
+    FxaaFloat fxaaConsoleEdgeThreshold,
+    FxaaFloat fxaaConsoleEdgeThresholdMin,
+    FxaaFloat4 fxaaConsole360ConstDir
+) {
+/*--------------------------------------------------------------------------*/
+    float4 lumaNwNeSwSe;
+    #if (FXAA_GREEN_AS_LUMA == 0)
+        asm { 
+            tfetch2D lumaNwNeSwSe.w___, tex, pos.xy, OffsetX = -0.5, OffsetY = -0.5, UseComputedLOD=false
+            tfetch2D lumaNwNeSwSe._w__, tex, pos.xy, OffsetX =  0.5, OffsetY = -0.5, UseComputedLOD=false
+            tfetch2D lumaNwNeSwSe.__w_, tex, pos.xy, OffsetX = -0.5, OffsetY =  0.5, UseComputedLOD=false
+            tfetch2D lumaNwNeSwSe.___w, tex, pos.xy, OffsetX =  0.5, OffsetY =  0.5, UseComputedLOD=false
+        };
+    #else
+        asm { 
+            tfetch2D lumaNwNeSwSe.y___, tex, pos.xy, OffsetX = -0.5, OffsetY = -0.5, UseComputedLOD=false
+            tfetch2D lumaNwNeSwSe._y__, tex, pos.xy, OffsetX =  0.5, OffsetY = -0.5, UseComputedLOD=false
+            tfetch2D lumaNwNeSwSe.__y_, tex, pos.xy, OffsetX = -0.5, OffsetY =  0.5, UseComputedLOD=false
+            tfetch2D lumaNwNeSwSe.___y, tex, pos.xy, OffsetX =  0.5, OffsetY =  0.5, UseComputedLOD=false
+        };
+    #endif
+/*--------------------------------------------------------------------------*/
+    lumaNwNeSwSe.y += 1.0/384.0;
+    float2 lumaMinTemp = min(lumaNwNeSwSe.xy, lumaNwNeSwSe.zw);
+    float2 lumaMaxTemp = max(lumaNwNeSwSe.xy, lumaNwNeSwSe.zw);
+    float lumaMin = min(lumaMinTemp.x, lumaMinTemp.y);
+    float lumaMax = max(lumaMaxTemp.x, lumaMaxTemp.y);
+/*--------------------------------------------------------------------------*/
+    float4 rgbyM = tex2Dlod(tex, float4(pos.xy, 0.0, 0.0));
+    #if (FXAA_GREEN_AS_LUMA == 0)
+        float lumaMinM = min(lumaMin, rgbyM.w);
+        float lumaMaxM = max(lumaMax, rgbyM.w);
+    #else
+        float lumaMinM = min(lumaMin, rgbyM.y);
+        float lumaMaxM = max(lumaMax, rgbyM.y);
+    #endif        
+    if((lumaMaxM - lumaMinM) < max(fxaaConsoleEdgeThresholdMin, lumaMax * fxaaConsoleEdgeThreshold)) return rgbyM;
+/*--------------------------------------------------------------------------*/
+    float2 dir;
+    dir.x = dot(lumaNwNeSwSe, fxaaConsole360ConstDir.yyxx);
+    dir.y = dot(lumaNwNeSwSe, fxaaConsole360ConstDir.xyxy);
+    dir = normalize(dir);
+/*--------------------------------------------------------------------------*/
+    float4 dir1 = dir.xyxy * fxaaConsoleRcpFrameOpt.xyzw;
+/*--------------------------------------------------------------------------*/
+    float4 dir2;
+    float dirAbsMinTimesC = min(abs(dir.x), abs(dir.y)) * fxaaConsoleEdgeSharpness;
+    dir2 = saturate(fxaaConsole360ConstDir.zzww * dir.xyxy / dirAbsMinTimesC + 0.5);
+    dir2 = dir2 * fxaaConsole360RcpFrameOpt2.xyxy + fxaaConsole360RcpFrameOpt2.zwzw;
+/*--------------------------------------------------------------------------*/
+    float4 rgbyN1 = tex2Dlod(fxaaConsole360TexExpBiasNegOne, float4(pos.xy + dir1.xy, 0.0, 0.0));
+    float4 rgbyP1 = tex2Dlod(fxaaConsole360TexExpBiasNegOne, float4(pos.xy + dir1.zw, 0.0, 0.0));
+    float4 rgbyN2 = tex2Dlod(fxaaConsole360TexExpBiasNegTwo, float4(pos.xy + dir2.xy, 0.0, 0.0));
+    float4 rgbyP2 = tex2Dlod(fxaaConsole360TexExpBiasNegTwo, float4(pos.xy + dir2.zw, 0.0, 0.0));
+/*--------------------------------------------------------------------------*/
+    float4 rgbyA = rgbyN1 + rgbyP1;
+    float4 rgbyB = rgbyN2 + rgbyP2 + rgbyA * 0.5;
+/*--------------------------------------------------------------------------*/
+    float4 rgbyR = ((FxaaLuma(rgbyB) - lumaMax) > 0.0) ? rgbyA : rgbyB; 
+    rgbyR = ((FxaaLuma(rgbyB) - lumaMin) > 0.0) ? rgbyR : rgbyA; 
+    return rgbyR; }
+/*==========================================================================*/
+#endif
+
+
+
+/*============================================================================
+
+         FXAA3 CONSOLE - OPTIMIZED PS3 PIXEL SHADER (NO EARLY EXIT)
+
+==============================================================================
+The code below does not exactly match the assembly.
+I have a feeling that 12 cycles is possible, but was not able to get there.
+Might have to increase register count to get full performance.
+Note this shader does not use perspective interpolation.
+
+Use the following cgc options,
+
+  --fenable-bx2 --fastmath --fastprecision --nofloatbindings
+
+------------------------------------------------------------------------------
+                             NVSHADERPERF OUTPUT
+------------------------------------------------------------------------------
+For reference and to aid in debug, output of NVShaderPerf should match this,
+
+Shader to schedule:
+  0: texpkb h0.w(TRUE), v5.zyxx, #0
+  2: addh h2.z(TRUE), h0.w, constant(0.001953, 0.000000, 0.000000, 0.000000).x
+  4: texpkb h0.w(TRUE), v5.xwxx, #0
+  6: addh h0.z(TRUE), -h2, h0.w
+  7: texpkb h1.w(TRUE), v5, #0
+  9: addh h0.x(TRUE), h0.z, -h1.w
+ 10: addh h3.w(TRUE), h0.z, h1
+ 11: texpkb h2.w(TRUE), v5.zwzz, #0
+ 13: addh h0.z(TRUE), h3.w, -h2.w
+ 14: addh h0.x(TRUE), h2.w, h0
+ 15: nrmh h1.xz(TRUE), h0_n
+ 16: minh_m8 h0.x(TRUE), |h1|, |h1.z|
+ 17: maxh h4.w(TRUE), h0, h1
+ 18: divx h2.xy(TRUE), h1_n.xzzw, h0_n
+ 19: movr r1.zw(TRUE), v4.xxxy
+ 20: madr r2.xz(TRUE), -h1, constant(cConst5.x, cConst5.y, cConst5.z, cConst5.w).zzww, r1.zzww
+ 22: minh h5.w(TRUE), h0, h1
+ 23: texpkb h0(TRUE), r2.xzxx, #0
+ 25: madr r0.zw(TRUE), h1.xzxz, constant(cConst5.x, cConst5.y, cConst5.z, cConst5.w), r1
+ 27: maxh h4.x(TRUE), h2.z, h2.w
+ 28: texpkb h1(TRUE), r0.zwzz, #0
+ 30: addh_d2 h1(TRUE), h0, h1
+ 31: madr r0.xy(TRUE), -h2, constant(cConst5.x, cConst5.y, cConst5.z, cConst5.w).xyxx, r1.zwzz
+ 33: texpkb h0(TRUE), r0, #0
+ 35: minh h4.z(TRUE), h2, h2.w
+ 36: fenct TRUE
+ 37: madr r1.xy(TRUE), h2, constant(cConst5.x, cConst5.y, cConst5.z, cConst5.w).xyxx, r1.zwzz
+ 39: texpkb h2(TRUE), r1, #0
+ 41: addh_d2 h0(TRUE), h0, h2
+ 42: maxh h2.w(TRUE), h4, h4.x
+ 43: minh h2.x(TRUE), h5.w, h4.z
+ 44: addh_d2 h0(TRUE), h0, h1
+ 45: slth h2.x(TRUE), h0.w, h2
+ 46: sgth h2.w(TRUE), h0, h2
+ 47: movh h0(TRUE), h0
+ 48: addx.c0 rc(TRUE), h2, h2.w
+ 49: movh h0(c0.NE.x), h1
+
+IPU0 ------ Simplified schedule: --------
+Pass |  Unit  |  uOp |  PC:  Op
+-----+--------+------+-------------------------
+   1 | SCT0/1 |  mov |   0:  TXLr h0.w, g[TEX1].zyxx, const.xxxx, TEX0;
+     |    TEX |  txl |   0:  TXLr h0.w, g[TEX1].zyxx, const.xxxx, TEX0;
+     |   SCB1 |  add |   2:  ADDh h2.z, h0.--w-, const.--x-;
+     |        |      |
+   2 | SCT0/1 |  mov |   4:  TXLr h0.w, g[TEX1].xwxx, const.xxxx, TEX0;
+     |    TEX |  txl |   4:  TXLr h0.w, g[TEX1].xwxx, const.xxxx, TEX0;
+     |   SCB1 |  add |   6:  ADDh h0.z,-h2, h0.--w-;
+     |        |      |
+   3 | SCT0/1 |  mov |   7:  TXLr h1.w, g[TEX1], const.xxxx, TEX0;
+     |    TEX |  txl |   7:  TXLr h1.w, g[TEX1], const.xxxx, TEX0;
+     |   SCB0 |  add |   9:  ADDh h0.x, h0.z---,-h1.w---;
+     |   SCB1 |  add |  10:  ADDh h3.w, h0.---z, h1;
+     |        |      |
+   4 | SCT0/1 |  mov |  11:  TXLr h2.w, g[TEX1].zwzz, const.xxxx, TEX0;
+     |    TEX |  txl |  11:  TXLr h2.w, g[TEX1].zwzz, const.xxxx, TEX0;
+     |   SCB0 |  add |  14:  ADDh h0.x, h2.w---, h0;
+     |   SCB1 |  add |  13:  ADDh h0.z, h3.--w-,-h2.--w-;
+     |        |      |
+   5 |   SCT1 |  mov |  15:  NRMh h1.xz, h0;
+     |    SRB |  nrm |  15:  NRMh h1.xz, h0;
+     |   SCB0 |  min |  16:  MINh*8 h0.x, |h1|, |h1.z---|;
+     |   SCB1 |  max |  17:  MAXh h4.w, h0, h1;
+     |        |      |
+   6 |   SCT0 |  div |  18:  DIVx h2.xy, h1.xz--, h0;
+     |   SCT1 |  mov |  19:  MOVr r1.zw, g[TEX0].--xy;
+     |   SCB0 |  mad |  20:  MADr r2.xz,-h1, const.z-w-, r1.z-w-;
+     |   SCB1 |  min |  22:  MINh h5.w, h0, h1;
+     |        |      |
+   7 | SCT0/1 |  mov |  23:  TXLr h0, r2.xzxx, const.xxxx, TEX0;
+     |    TEX |  txl |  23:  TXLr h0, r2.xzxx, const.xxxx, TEX0;
+     |   SCB0 |  max |  27:  MAXh h4.x, h2.z---, h2.w---;
+     |   SCB1 |  mad |  25:  MADr r0.zw, h1.--xz, const, r1;
+     |        |      |
+   8 | SCT0/1 |  mov |  28:  TXLr h1, r0.zwzz, const.xxxx, TEX0;
+     |    TEX |  txl |  28:  TXLr h1, r0.zwzz, const.xxxx, TEX0;
+     | SCB0/1 |  add |  30:  ADDh/2 h1, h0, h1;
+     |        |      |
+   9 |   SCT0 |  mad |  31:  MADr r0.xy,-h2, const.xy--, r1.zw--;
+     |   SCT1 |  mov |  33:  TXLr h0, r0, const.zzzz, TEX0;
+     |    TEX |  txl |  33:  TXLr h0, r0, const.zzzz, TEX0;
+     |   SCB1 |  min |  35:  MINh h4.z, h2, h2.--w-;
+     |        |      |
+  10 |   SCT0 |  mad |  37:  MADr r1.xy, h2, const.xy--, r1.zw--;
+     |   SCT1 |  mov |  39:  TXLr h2, r1, const.zzzz, TEX0;
+     |    TEX |  txl |  39:  TXLr h2, r1, const.zzzz, TEX0;
+     | SCB0/1 |  add |  41:  ADDh/2 h0, h0, h2;
+     |        |      |
+  11 |   SCT0 |  min |  43:  MINh h2.x, h5.w---, h4.z---;
+     |   SCT1 |  max |  42:  MAXh h2.w, h4, h4.---x;
+     | SCB0/1 |  add |  44:  ADDh/2 h0, h0, h1;
+     |        |      |
+  12 |   SCT0 |  set |  45:  SLTh h2.x, h0.w---, h2;
+     |   SCT1 |  set |  46:  SGTh h2.w, h0, h2;
+     | SCB0/1 |  mul |  47:  MOVh h0, h0;
+     |        |      |
+  13 |   SCT0 |  mad |  48:  ADDxc0_s rc, h2, h2.w---;
+     | SCB0/1 |  mul |  49:  MOVh h0(NE0.xxxx), h1;
+ 
+Pass   SCT  TEX  SCB
+  1:   0% 100%  25%
+  2:   0% 100%  25%
+  3:   0% 100%  50%
+  4:   0% 100%  50%
+  5:   0%   0%  50%
+  6: 100%   0%  75%
+  7:   0% 100%  75%
+  8:   0% 100% 100%
+  9:   0% 100%  25%
+ 10:   0% 100% 100%
+ 11:  50%   0% 100%
+ 12:  50%   0% 100%
+ 13:  25%   0% 100%
+
+MEAN:  17%  61%  67%
+
+Pass   SCT0  SCT1   TEX  SCB0  SCB1
+  1:    0%    0%  100%    0%  100%
+  2:    0%    0%  100%    0%  100%
+  3:    0%    0%  100%  100%  100%
+  4:    0%    0%  100%  100%  100%
+  5:    0%    0%    0%  100%  100%
+  6:  100%  100%    0%  100%  100%
+  7:    0%    0%  100%  100%  100%
+  8:    0%    0%  100%  100%  100%
+  9:    0%    0%  100%    0%  100%
+ 10:    0%    0%  100%  100%  100%
+ 11:  100%  100%    0%  100%  100%
+ 12:  100%  100%    0%  100%  100%
+ 13:  100%    0%    0%  100%  100%
+
+MEAN:   30%   23%   61%   76%  100%
+Fragment Performance Setup: Driver RSX Compiler, GPU RSX, Flags 0x5
+Results 13 cycles, 3 r regs, 923,076,923 pixels/s
+============================================================================*/
+#if (FXAA_PS3 == 1) && (FXAA_EARLY_EXIT == 0)
+/*--------------------------------------------------------------------------*/
+#pragma regcount 7
+#pragma disablepc all
+#pragma option O3
+#pragma option OutColorPrec=fp16
+#pragma texformat default RGBA8
+/*==========================================================================*/
+half4 FxaaPixelShader(
+    // See FXAA Quality FxaaPixelShader() source for docs on Inputs!
+    FxaaFloat2 pos,
+    FxaaFloat4 fxaaConsolePosPos,
+    FxaaTex tex,
+    FxaaTex fxaaConsole360TexExpBiasNegOne,
+    FxaaTex fxaaConsole360TexExpBiasNegTwo,
+    FxaaFloat2 fxaaQualityRcpFrame,
+    FxaaFloat4 fxaaConsoleRcpFrameOpt,
+    FxaaFloat4 fxaaConsoleRcpFrameOpt2,
+    FxaaFloat4 fxaaConsole360RcpFrameOpt2,
+    FxaaFloat fxaaQualitySubpix,
+    FxaaFloat fxaaQualityEdgeThreshold,
+    FxaaFloat fxaaQualityEdgeThresholdMin,
+    FxaaFloat fxaaConsoleEdgeSharpness,
+    FxaaFloat fxaaConsoleEdgeThreshold,
+    FxaaFloat fxaaConsoleEdgeThresholdMin,
+    FxaaFloat4 fxaaConsole360ConstDir
+) {
+/*--------------------------------------------------------------------------*/
+// (1)
+    half4 dir;
+    half4 lumaNe = h4tex2Dlod(tex, half4(fxaaConsolePosPos.zy, 0, 0));
+    #if (FXAA_GREEN_AS_LUMA == 0)
+        lumaNe.w += half(1.0/512.0);
+        dir.x = -lumaNe.w;
+        dir.z = -lumaNe.w;
+    #else
+        lumaNe.y += half(1.0/512.0);
+        dir.x = -lumaNe.y;
+        dir.z = -lumaNe.y;
+    #endif
+/*--------------------------------------------------------------------------*/
+// (2)
+    half4 lumaSw = h4tex2Dlod(tex, half4(fxaaConsolePosPos.xw, 0, 0));
+    #if (FXAA_GREEN_AS_LUMA == 0)
+        dir.x += lumaSw.w;
+        dir.z += lumaSw.w;
+    #else
+        dir.x += lumaSw.y;
+        dir.z += lumaSw.y;
+    #endif        
+/*--------------------------------------------------------------------------*/
+// (3)
+    half4 lumaNw = h4tex2Dlod(tex, half4(fxaaConsolePosPos.xy, 0, 0));
+    #if (FXAA_GREEN_AS_LUMA == 0)
+        dir.x -= lumaNw.w;
+        dir.z += lumaNw.w;
+    #else
+        dir.x -= lumaNw.y;
+        dir.z += lumaNw.y;
+    #endif
+/*--------------------------------------------------------------------------*/
+// (4)
+    half4 lumaSe = h4tex2Dlod(tex, half4(fxaaConsolePosPos.zw, 0, 0));
+    #if (FXAA_GREEN_AS_LUMA == 0)
+        dir.x += lumaSe.w;
+        dir.z -= lumaSe.w;
+    #else
+        dir.x += lumaSe.y;
+        dir.z -= lumaSe.y;
+    #endif
+/*--------------------------------------------------------------------------*/
+// (5)
+    half4 dir1_pos;
+    dir1_pos.xy = normalize(dir.xyz).xz;
+    half dirAbsMinTimesC = min(abs(dir1_pos.x), abs(dir1_pos.y)) * half(FXAA_CONSOLE_PS3_EDGE_SHARPNESS);
+/*--------------------------------------------------------------------------*/
+// (6)
+    half4 dir2_pos;
+    dir2_pos.xy = clamp(dir1_pos.xy / dirAbsMinTimesC, half(-2.0), half(2.0));
+    dir1_pos.zw = pos.xy;
+    dir2_pos.zw = pos.xy;
+    half4 temp1N;
+    temp1N.xy = dir1_pos.zw - dir1_pos.xy * fxaaConsoleRcpFrameOpt.zw;
+/*--------------------------------------------------------------------------*/
+// (7)
+    temp1N = h4tex2Dlod(tex, half4(temp1N.xy, 0.0, 0.0));
+    half4 rgby1;
+    rgby1.xy = dir1_pos.zw + dir1_pos.xy * fxaaConsoleRcpFrameOpt.zw;
+/*--------------------------------------------------------------------------*/
+// (8)
+    rgby1 = h4tex2Dlod(tex, half4(rgby1.xy, 0.0, 0.0));
+    rgby1 = (temp1N + rgby1) * 0.5;
+/*--------------------------------------------------------------------------*/
+// (9)
+    half4 temp2N;
+    temp2N.xy = dir2_pos.zw - dir2_pos.xy * fxaaConsoleRcpFrameOpt2.zw;
+    temp2N = h4tex2Dlod(tex, half4(temp2N.xy, 0.0, 0.0));
+/*--------------------------------------------------------------------------*/
+// (10)
+    half4 rgby2;
+    rgby2.xy = dir2_pos.zw + dir2_pos.xy * fxaaConsoleRcpFrameOpt2.zw;
+    rgby2 = h4tex2Dlod(tex, half4(rgby2.xy, 0.0, 0.0));
+    rgby2 = (temp2N + rgby2) * 0.5;
+/*--------------------------------------------------------------------------*/
+// (11)
+    // compilier moves these scalar ops up to other cycles
+    #if (FXAA_GREEN_AS_LUMA == 0)
+        half lumaMin = min(min(lumaNw.w, lumaSw.w), min(lumaNe.w, lumaSe.w));
+        half lumaMax = max(max(lumaNw.w, lumaSw.w), max(lumaNe.w, lumaSe.w));
+    #else
+        half lumaMin = min(min(lumaNw.y, lumaSw.y), min(lumaNe.y, lumaSe.y));
+        half lumaMax = max(max(lumaNw.y, lumaSw.y), max(lumaNe.y, lumaSe.y));
+    #endif        
+    rgby2 = (rgby2 + rgby1) * 0.5;
+/*--------------------------------------------------------------------------*/
+// (12)
+    #if (FXAA_GREEN_AS_LUMA == 0)
+        bool twoTapLt = rgby2.w < lumaMin;
+        bool twoTapGt = rgby2.w > lumaMax;
+    #else
+        bool twoTapLt = rgby2.y < lumaMin;
+        bool twoTapGt = rgby2.y > lumaMax;
+    #endif
+/*--------------------------------------------------------------------------*/
+// (13)
+    if(twoTapLt || twoTapGt) rgby2 = rgby1;
+/*--------------------------------------------------------------------------*/
+    return rgby2; }
+/*==========================================================================*/
+#endif
+
+
+
+/*============================================================================
+
+       FXAA3 CONSOLE - OPTIMIZED PS3 PIXEL SHADER (WITH EARLY EXIT)
+
+==============================================================================
+The code mostly matches the assembly.
+I have a feeling that 14 cycles is possible, but was not able to get there.
+Might have to increase register count to get full performance.
+Note this shader does not use perspective interpolation.
+
+Use the following cgc options,
+
+ --fenable-bx2 --fastmath --fastprecision --nofloatbindings
+
+Use of FXAA_GREEN_AS_LUMA currently adds a cycle (16 clks).
+Will look at fixing this for FXAA 3.12.
+------------------------------------------------------------------------------
+                             NVSHADERPERF OUTPUT
+------------------------------------------------------------------------------
+For reference and to aid in debug, output of NVShaderPerf should match this,
+
+Shader to schedule:
+  0: texpkb h0.w(TRUE), v5.zyxx, #0
+  2: addh h2.y(TRUE), h0.w, constant(0.001953, 0.000000, 0.000000, 0.000000).x
+  4: texpkb h1.w(TRUE), v5.xwxx, #0
+  6: addh h0.x(TRUE), h1.w, -h2.y
+  7: texpkb h2.w(TRUE), v5.zwzz, #0
+  9: minh h4.w(TRUE), h2.y, h2
+ 10: maxh h5.x(TRUE), h2.y, h2.w
+ 11: texpkb h0.w(TRUE), v5, #0
+ 13: addh h3.w(TRUE), -h0, h0.x
+ 14: addh h0.x(TRUE), h0.w, h0
+ 15: addh h0.z(TRUE), -h2.w, h0.x
+ 16: addh h0.x(TRUE), h2.w, h3.w
+ 17: minh h5.y(TRUE), h0.w, h1.w
+ 18: nrmh h2.xz(TRUE), h0_n
+ 19: minh_m8 h2.w(TRUE), |h2.x|, |h2.z|
+ 20: divx h4.xy(TRUE), h2_n.xzzw, h2_n.w
+ 21: movr r1.zw(TRUE), v4.xxxy
+ 22: maxh h2.w(TRUE), h0, h1
+ 23: fenct TRUE
+ 24: madr r0.xy(TRUE), -h2.xzzw, constant(cConst5.x, cConst5.y, cConst5.z, cConst5.w).zwzz, r1.zwzz
+ 26: texpkb h0(TRUE), r0, #0
+ 28: maxh h5.x(TRUE), h2.w, h5
+ 29: minh h5.w(TRUE), h5.y, h4
+ 30: madr r1.xy(TRUE), h2.xzzw, constant(cConst5.x, cConst5.y, cConst5.z, cConst5.w).zwzz, r1.zwzz
+ 32: texpkb h2(TRUE), r1, #0
+ 34: addh_d2 h2(TRUE), h0, h2
+ 35: texpkb h1(TRUE), v4, #0
+ 37: maxh h5.y(TRUE), h5.x, h1.w
+ 38: minh h4.w(TRUE), h1, h5
+ 39: madr r0.xy(TRUE), -h4, constant(cConst5.x, cConst5.y, cConst5.z, cConst5.w).xyxx, r1.zwzz
+ 41: texpkb h0(TRUE), r0, #0
+ 43: addh_m8 h5.z(TRUE), h5.y, -h4.w
+ 44: madr r2.xy(TRUE), h4, constant(cConst5.x, cConst5.y, cConst5.z, cConst5.w).xyxx, r1.zwzz
+ 46: texpkb h3(TRUE), r2, #0
+ 48: addh_d2 h0(TRUE), h0, h3
+ 49: addh_d2 h3(TRUE), h0, h2
+ 50: movh h0(TRUE), h3
+ 51: slth h3.x(TRUE), h3.w, h5.w
+ 52: sgth h3.w(TRUE), h3, h5.x
+ 53: addx.c0 rc(TRUE), h3.x, h3
+ 54: slth.c0 rc(TRUE), h5.z, h5
+ 55: movh h0(c0.NE.w), h2
+ 56: movh h0(c0.NE.x), h1
+
+IPU0 ------ Simplified schedule: --------
+Pass |  Unit  |  uOp |  PC:  Op
+-----+--------+------+-------------------------
+   1 | SCT0/1 |  mov |   0:  TXLr h0.w, g[TEX1].zyxx, const.xxxx, TEX0;
+     |    TEX |  txl |   0:  TXLr h0.w, g[TEX1].zyxx, const.xxxx, TEX0;
+     |   SCB0 |  add |   2:  ADDh h2.y, h0.-w--, const.-x--;
+     |        |      |
+   2 | SCT0/1 |  mov |   4:  TXLr h1.w, g[TEX1].xwxx, const.xxxx, TEX0;
+     |    TEX |  txl |   4:  TXLr h1.w, g[TEX1].xwxx, const.xxxx, TEX0;
+     |   SCB0 |  add |   6:  ADDh h0.x, h1.w---,-h2.y---;
+     |        |      |
+   3 | SCT0/1 |  mov |   7:  TXLr h2.w, g[TEX1].zwzz, const.xxxx, TEX0;
+     |    TEX |  txl |   7:  TXLr h2.w, g[TEX1].zwzz, const.xxxx, TEX0;
+     |   SCB0 |  max |  10:  MAXh h5.x, h2.y---, h2.w---;
+     |   SCB1 |  min |   9:  MINh h4.w, h2.---y, h2;
+     |        |      |
+   4 | SCT0/1 |  mov |  11:  TXLr h0.w, g[TEX1], const.xxxx, TEX0;
+     |    TEX |  txl |  11:  TXLr h0.w, g[TEX1], const.xxxx, TEX0;
+     |   SCB0 |  add |  14:  ADDh h0.x, h0.w---, h0;
+     |   SCB1 |  add |  13:  ADDh h3.w,-h0, h0.---x;
+     |        |      |
+   5 |   SCT0 |  mad |  16:  ADDh h0.x, h2.w---, h3.w---;
+     |   SCT1 |  mad |  15:  ADDh h0.z,-h2.--w-, h0.--x-;
+     |   SCB0 |  min |  17:  MINh h5.y, h0.-w--, h1.-w--;
+     |        |      |
+   6 |   SCT1 |  mov |  18:  NRMh h2.xz, h0;
+     |    SRB |  nrm |  18:  NRMh h2.xz, h0;
+     |   SCB1 |  min |  19:  MINh*8 h2.w, |h2.---x|, |h2.---z|;
+     |        |      |
+   7 |   SCT0 |  div |  20:  DIVx h4.xy, h2.xz--, h2.ww--;
+     |   SCT1 |  mov |  21:  MOVr r1.zw, g[TEX0].--xy;
+     |   SCB1 |  max |  22:  MAXh h2.w, h0, h1;
+     |        |      |
+   8 |   SCT0 |  mad |  24:  MADr r0.xy,-h2.xz--, const.zw--, r1.zw--;
+     |   SCT1 |  mov |  26:  TXLr h0, r0, const.xxxx, TEX0;
+     |    TEX |  txl |  26:  TXLr h0, r0, const.xxxx, TEX0;
+     |   SCB0 |  max |  28:  MAXh h5.x, h2.w---, h5;
+     |   SCB1 |  min |  29:  MINh h5.w, h5.---y, h4;
+     |        |      |
+   9 |   SCT0 |  mad |  30:  MADr r1.xy, h2.xz--, const.zw--, r1.zw--;
+     |   SCT1 |  mov |  32:  TXLr h2, r1, const.xxxx, TEX0;
+     |    TEX |  txl |  32:  TXLr h2, r1, const.xxxx, TEX0;
+     | SCB0/1 |  add |  34:  ADDh/2 h2, h0, h2;
+     |        |      |
+  10 | SCT0/1 |  mov |  35:  TXLr h1, g[TEX0], const.xxxx, TEX0;
+     |    TEX |  txl |  35:  TXLr h1, g[TEX0], const.xxxx, TEX0;
+     |   SCB0 |  max |  37:  MAXh h5.y, h5.-x--, h1.-w--;
+     |   SCB1 |  min |  38:  MINh h4.w, h1, h5;
+     |        |      |
+  11 |   SCT0 |  mad |  39:  MADr r0.xy,-h4, const.xy--, r1.zw--;
+     |   SCT1 |  mov |  41:  TXLr h0, r0, const.zzzz, TEX0;
+     |    TEX |  txl |  41:  TXLr h0, r0, const.zzzz, TEX0;
+     |   SCB0 |  mad |  44:  MADr r2.xy, h4, const.xy--, r1.zw--;
+     |   SCB1 |  add |  43:  ADDh*8 h5.z, h5.--y-,-h4.--w-;
+     |        |      |
+  12 | SCT0/1 |  mov |  46:  TXLr h3, r2, const.xxxx, TEX0;
+     |    TEX |  txl |  46:  TXLr h3, r2, const.xxxx, TEX0;
+     | SCB0/1 |  add |  48:  ADDh/2 h0, h0, h3;
+     |        |      |
+  13 | SCT0/1 |  mad |  49:  ADDh/2 h3, h0, h2;
+     | SCB0/1 |  mul |  50:  MOVh h0, h3;
+     |        |      |
+  14 |   SCT0 |  set |  51:  SLTh h3.x, h3.w---, h5.w---;
+     |   SCT1 |  set |  52:  SGTh h3.w, h3, h5.---x;
+     |   SCB0 |  set |  54:  SLThc0 rc, h5.z---, h5;
+     |   SCB1 |  add |  53:  ADDxc0_s rc, h3.---x, h3;
+     |        |      |
+  15 | SCT0/1 |  mul |  55:  MOVh h0(NE0.wwww), h2;
+     | SCB0/1 |  mul |  56:  MOVh h0(NE0.xxxx), h1;
+ 
+Pass   SCT  TEX  SCB
+  1:   0% 100%  25%
+  2:   0% 100%  25%
+  3:   0% 100%  50%
+  4:   0% 100%  50%
+  5:  50%   0%  25%
+  6:   0%   0%  25%
+  7: 100%   0%  25%
+  8:   0% 100%  50%
+  9:   0% 100% 100%
+ 10:   0% 100%  50%
+ 11:   0% 100%  75%
+ 12:   0% 100% 100%
+ 13: 100%   0% 100%
+ 14:  50%   0%  50%
+ 15: 100%   0% 100%
+
+MEAN:  26%  60%  56%
+
+Pass   SCT0  SCT1   TEX  SCB0  SCB1
+  1:    0%    0%  100%  100%    0%
+  2:    0%    0%  100%  100%    0%
+  3:    0%    0%  100%  100%  100%
+  4:    0%    0%  100%  100%  100%
+  5:  100%  100%    0%  100%    0%
+  6:    0%    0%    0%    0%  100%
+  7:  100%  100%    0%    0%  100%
+  8:    0%    0%  100%  100%  100%
+  9:    0%    0%  100%  100%  100%
+ 10:    0%    0%  100%  100%  100%
+ 11:    0%    0%  100%  100%  100%
+ 12:    0%    0%  100%  100%  100%
+ 13:  100%  100%    0%  100%  100%
+ 14:  100%  100%    0%  100%  100%
+ 15:  100%  100%    0%  100%  100%
+
+MEAN:   33%   33%   60%   86%   80%
+Fragment Performance Setup: Driver RSX Compiler, GPU RSX, Flags 0x5
+Results 15 cycles, 3 r regs, 800,000,000 pixels/s
+============================================================================*/
+#if (FXAA_PS3 == 1) && (FXAA_EARLY_EXIT == 1)
+/*--------------------------------------------------------------------------*/
+#pragma regcount 7
+#pragma disablepc all
+#pragma option O2
+#pragma option OutColorPrec=fp16
+#pragma texformat default RGBA8
+/*==========================================================================*/
+half4 FxaaPixelShader(
+    // See FXAA Quality FxaaPixelShader() source for docs on Inputs!
+    FxaaFloat2 pos,
+    FxaaFloat4 fxaaConsolePosPos,
+    FxaaTex tex,
+    FxaaTex fxaaConsole360TexExpBiasNegOne,
+    FxaaTex fxaaConsole360TexExpBiasNegTwo,
+    FxaaFloat2 fxaaQualityRcpFrame,
+    FxaaFloat4 fxaaConsoleRcpFrameOpt,
+    FxaaFloat4 fxaaConsoleRcpFrameOpt2,
+    FxaaFloat4 fxaaConsole360RcpFrameOpt2,
+    FxaaFloat fxaaQualitySubpix,
+    FxaaFloat fxaaQualityEdgeThreshold,
+    FxaaFloat fxaaQualityEdgeThresholdMin,
+    FxaaFloat fxaaConsoleEdgeSharpness,
+    FxaaFloat fxaaConsoleEdgeThreshold,
+    FxaaFloat fxaaConsoleEdgeThresholdMin,
+    FxaaFloat4 fxaaConsole360ConstDir
+) {
+/*--------------------------------------------------------------------------*/
+// (1)
+    half4 rgbyNe = h4tex2Dlod(tex, half4(fxaaConsolePosPos.zy, 0, 0));
+    #if (FXAA_GREEN_AS_LUMA == 0)
+        half lumaNe = rgbyNe.w + half(1.0/512.0);
+    #else
+        half lumaNe = rgbyNe.y + half(1.0/512.0);
+    #endif
+/*--------------------------------------------------------------------------*/
+// (2)
+    half4 lumaSw = h4tex2Dlod(tex, half4(fxaaConsolePosPos.xw, 0, 0));
+    #if (FXAA_GREEN_AS_LUMA == 0)
+        half lumaSwNegNe = lumaSw.w - lumaNe;
+    #else
+        half lumaSwNegNe = lumaSw.y - lumaNe;
+    #endif
+/*--------------------------------------------------------------------------*/
+// (3)
+    half4 lumaNw = h4tex2Dlod(tex, half4(fxaaConsolePosPos.xy, 0, 0));
+    #if (FXAA_GREEN_AS_LUMA == 0)
+        half lumaMaxNwSw = max(lumaNw.w, lumaSw.w);
+        half lumaMinNwSw = min(lumaNw.w, lumaSw.w);
+    #else
+        half lumaMaxNwSw = max(lumaNw.y, lumaSw.y);
+        half lumaMinNwSw = min(lumaNw.y, lumaSw.y);
+    #endif
+/*--------------------------------------------------------------------------*/
+// (4)
+    half4 lumaSe = h4tex2Dlod(tex, half4(fxaaConsolePosPos.zw, 0, 0));
+    #if (FXAA_GREEN_AS_LUMA == 0)
+        half dirZ =  lumaNw.w + lumaSwNegNe;
+        half dirX = -lumaNw.w + lumaSwNegNe;
+    #else
+        half dirZ =  lumaNw.y + lumaSwNegNe;
+        half dirX = -lumaNw.y + lumaSwNegNe;
+    #endif
+/*--------------------------------------------------------------------------*/
+// (5)
+    half3 dir;
+    dir.y = 0.0;
+    #if (FXAA_GREEN_AS_LUMA == 0)
+        dir.x =  lumaSe.w + dirX;
+        dir.z = -lumaSe.w + dirZ;
+        half lumaMinNeSe = min(lumaNe, lumaSe.w);
+    #else
+        dir.x =  lumaSe.y + dirX;
+        dir.z = -lumaSe.y + dirZ;
+        half lumaMinNeSe = min(lumaNe, lumaSe.y);
+    #endif
+/*--------------------------------------------------------------------------*/
+// (6)
+    half4 dir1_pos;
+    dir1_pos.xy = normalize(dir).xz;
+    half dirAbsMinTimes8 = min(abs(dir1_pos.x), abs(dir1_pos.y)) * half(FXAA_CONSOLE_PS3_EDGE_SHARPNESS);
+/*--------------------------------------------------------------------------*/
+// (7)
+    half4 dir2_pos;
+    dir2_pos.xy = clamp(dir1_pos.xy / dirAbsMinTimes8, half(-2.0), half(2.0));
+    dir1_pos.zw = pos.xy;
+    dir2_pos.zw = pos.xy;
+    #if (FXAA_GREEN_AS_LUMA == 0)
+        half lumaMaxNeSe = max(lumaNe, lumaSe.w);
+    #else
+        half lumaMaxNeSe = max(lumaNe, lumaSe.y);
+    #endif
+/*--------------------------------------------------------------------------*/
+// (8)
+    half4 temp1N;
+    temp1N.xy = dir1_pos.zw - dir1_pos.xy * fxaaConsoleRcpFrameOpt.zw;
+    temp1N = h4tex2Dlod(tex, half4(temp1N.xy, 0.0, 0.0));
+    half lumaMax = max(lumaMaxNwSw, lumaMaxNeSe);
+    half lumaMin = min(lumaMinNwSw, lumaMinNeSe);
+/*--------------------------------------------------------------------------*/
+// (9)
+    half4 rgby1;
+    rgby1.xy = dir1_pos.zw + dir1_pos.xy * fxaaConsoleRcpFrameOpt.zw;
+    rgby1 = h4tex2Dlod(tex, half4(rgby1.xy, 0.0, 0.0));
+    rgby1 = (temp1N + rgby1) * 0.5;
+/*--------------------------------------------------------------------------*/
+// (10)
+    half4 rgbyM = h4tex2Dlod(tex, half4(pos.xy, 0.0, 0.0));
+    #if (FXAA_GREEN_AS_LUMA == 0)
+        half lumaMaxM = max(lumaMax, rgbyM.w);
+        half lumaMinM = min(lumaMin, rgbyM.w);
+    #else
+        half lumaMaxM = max(lumaMax, rgbyM.y);
+        half lumaMinM = min(lumaMin, rgbyM.y);
+    #endif
+/*--------------------------------------------------------------------------*/
+// (11)
+    half4 temp2N;
+    temp2N.xy = dir2_pos.zw - dir2_pos.xy * fxaaConsoleRcpFrameOpt2.zw;
+    temp2N = h4tex2Dlod(tex, half4(temp2N.xy, 0.0, 0.0));
+    half4 rgby2;
+    rgby2.xy = dir2_pos.zw + dir2_pos.xy * fxaaConsoleRcpFrameOpt2.zw;
+    half lumaRangeM = (lumaMaxM - lumaMinM) / FXAA_CONSOLE_PS3_EDGE_THRESHOLD;
+/*--------------------------------------------------------------------------*/
+// (12)
+    rgby2 = h4tex2Dlod(tex, half4(rgby2.xy, 0.0, 0.0));
+    rgby2 = (temp2N + rgby2) * 0.5;
+/*--------------------------------------------------------------------------*/
+// (13)
+    rgby2 = (rgby2 + rgby1) * 0.5;
+/*--------------------------------------------------------------------------*/
+// (14)
+    #if (FXAA_GREEN_AS_LUMA == 0)
+        bool twoTapLt = rgby2.w < lumaMin;
+        bool twoTapGt = rgby2.w > lumaMax;
+    #else
+        bool twoTapLt = rgby2.y < lumaMin;
+        bool twoTapGt = rgby2.y > lumaMax;
+    #endif
+    bool earlyExit = lumaRangeM < lumaMax;
+    bool twoTap = twoTapLt || twoTapGt;
+/*--------------------------------------------------------------------------*/
+// (15)
+    if(twoTap) rgby2 = rgby1;
+    if(earlyExit) rgby2 = rgbyM;
+/*--------------------------------------------------------------------------*/
+    return rgby2; }
+/*==========================================================================*/
+#endif
+
+uniform sampler2D fb_color0;
+uniform vec2 fb_texel;
+noperspective varying vec2 texcoord; 
+
+void main(void) {
+    out_Color = FxaaPixelShader(
+    //
+    // Use noperspective interpolation here (turn off perspective interpolation).
+    // {xy} = center of pixel
+    texcoord, // FxaaFloat2 pos,
+    //
+    // Used only for FXAA Console, and not used on the 360 version.
+    // Use noperspective interpolation here (turn off perspective interpolation).
+    // {xy__} = upper left of pixel
+    // {__zw} = lower right of pixel
+    vec4(0.0), // FxaaFloat4 fxaaConsolePosPos,
+    //
+    // Input color texture.
+    // {rgb_} = color in linear or perceptual color space
+    // if (FXAA_GREEN_AS_LUMA == 0)
+    //     {___a} = luma in perceptual color space (not linear)
+    fb_color0, // FxaaTex tex,
+    //
+    // Only used on the optimized 360 version of FXAA Console.
+    // For everything but 360, just use the same input here as for "tex".
+    // For 360, same texture, just alias with a 2nd sampler.
+    // This sampler needs to have an exponent bias of -1.
+    fb_color0, // FxaaTex fxaaConsole360TexExpBiasNegOne,
+    //
+    // Only used on the optimized 360 version of FXAA Console.
+    // For everything but 360, just use the same input here as for "tex".
+    // For 360, same texture, just alias with a 3nd sampler.
+    // This sampler needs to have an exponent bias of -2.
+    fb_color0, // FxaaTex fxaaConsole360TexExpBiasNegTwo,
+    //
+    // Only used on FXAA Quality.
+    // This must be from a constant/uniform.
+    // {x_} = 1.0/screenWidthInPixels
+    // {_y} = 1.0/screenHeightInPixels
+    fb_texel, // FxaaFloat2 fxaaQualityRcpFrame,
+    //
+    // Only used on FXAA Console.
+    // This must be from a constant/uniform.
+    // This effects sub-pixel AA quality and inversely sharpness.
+    //   Where N ranges between,
+    //     N = 0.50 (default)
+    //     N = 0.33 (sharper)
+    // {x___} = -N/screenWidthInPixels  
+    // {_y__} = -N/screenHeightInPixels
+    // {__z_} =  N/screenWidthInPixels  
+    // {___w} =  N/screenHeightInPixels 
+    vec4(0.0), // FxaaFloat4 fxaaConsoleRcpFrameOpt,
+    //
+    // Only used on FXAA Console.
+    // Not used on 360, but used on PS3 and PC.
+    // This must be from a constant/uniform.
+    // {x___} = -2.0/screenWidthInPixels  
+    // {_y__} = -2.0/screenHeightInPixels
+    // {__z_} =  2.0/screenWidthInPixels  
+    // {___w} =  2.0/screenHeightInPixels 
+    vec4(0.0), // FxaaFloat4 fxaaConsoleRcpFrameOpt2,
+    //
+    // Only used on FXAA Console.
+    // Only used on 360 in place of fxaaConsoleRcpFrameOpt2.
+    // This must be from a constant/uniform.
+    // {x___} =  8.0/screenWidthInPixels  
+    // {_y__} =  8.0/screenHeightInPixels
+    // {__z_} = -4.0/screenWidthInPixels  
+    // {___w} = -4.0/screenHeightInPixels 
+    vec4(0.0), // FxaaFloat4 fxaaConsole360RcpFrameOpt2,
+    //
+    // Only used on FXAA Quality.
+    // This used to be the FXAA_QUALITY_SUBPIX define.
+    // It is here now to allow easier tuning.
+    // Choose the amount of sub-pixel aliasing removal.
+    // This can effect sharpness.
+    //   1.00 - upper limit (softer)
+    //   0.75 - default amount of filtering
+    //   0.50 - lower limit (sharper, less sub-pixel aliasing removal)
+    //   0.25 - almost off
+    //   0.00 - completely off
+    1.0, // FxaaFloat fxaaQualitySubpix,
+    //
+    // Only used on FXAA Quality.
+    // This used to be the FXAA_QUALITY_EDGE_THRESHOLD define.
+    // It is here now to allow easier tuning.
+    // The minimum amount of local contrast required to apply algorithm.
+    //   0.333 - too little (faster)
+    //   0.250 - low quality
+    //   0.166 - default
+    //   0.125 - high quality 
+    //   0.063 - overkill (slower)
+    0.063, // FxaaFloat fxaaQualityEdgeThreshold,
+    //
+    // Only used on FXAA Quality.
+    // This used to be the FXAA_QUALITY_EDGE_THRESHOLD_MIN define.
+    // It is here now to allow easier tuning.
+    // Trims the algorithm from processing darks.
+    //   0.0833 - upper limit (default, the start of visible unfiltered edges)
+    //   0.0625 - high quality (faster)
+    //   0.0312 - visible limit (slower)
+    // Special notes when using FXAA_GREEN_AS_LUMA,
+    //   Likely want to set this to zero.
+    //   As colors that are mostly not-green
+    //   will appear very dark in the green channel!
+    //   Tune by looking at mostly non-green content,
+    //   then start at zero and increase until aliasing is a problem.
+    0.0312, // FxaaFloat fxaaQualityEdgeThresholdMin,
+    // 
+    // Only used on FXAA Console.
+    // This used to be the FXAA_CONSOLE_EDGE_SHARPNESS define.
+    // It is here now to allow easier tuning.
+    // This does not effect PS3, as this needs to be compiled in.
+    //   Use FXAA_CONSOLE_PS3_EDGE_SHARPNESS for PS3.
+    //   Due to the PS3 being ALU bound,
+    //   there are only three safe values here: 2 and 4 and 8.
+    //   These options use the shaders ability to a free *|/ by 2|4|8.
+    // For all other platforms can be a non-power of two.
+    //   8.0 is sharper (default!!!)
+    //   4.0 is softer
+    //   2.0 is really soft (good only for vector graphics inputs)
+    8.0, // FxaaFloat fxaaConsoleEdgeSharpness,
+    //
+    // Only used on FXAA Console.
+    // This used to be the FXAA_CONSOLE_EDGE_THRESHOLD define.
+    // It is here now to allow easier tuning.
+    // This does not effect PS3, as this needs to be compiled in.
+    //   Use FXAA_CONSOLE_PS3_EDGE_THRESHOLD for PS3.
+    //   Due to the PS3 being ALU bound,
+    //   there are only two safe values here: 1/4 and 1/8.
+    //   These options use the shaders ability to a free *|/ by 2|4|8.
+    // The console setting has a different mapping than the quality setting.
+    // Other platforms can use other values.
+    //   0.125 leaves less aliasing, but is softer (default!!!)
+    //   0.25 leaves more aliasing, and is sharper
+    0.125, // FxaaFloat fxaaConsoleEdgeThreshold,
+    //
+    // Only used on FXAA Console.
+    // This used to be the FXAA_CONSOLE_EDGE_THRESHOLD_MIN define.
+    // It is here now to allow easier tuning.
+    // Trims the algorithm from processing darks.
+    // The console setting has a different mapping than the quality setting.
+    // This only applies when FXAA_EARLY_EXIT is 1.
+    // This does not apply to PS3, 
+    // PS3 was simplified to avoid more shader instructions.
+    //   0.06 - faster but more aliasing in darks
+    //   0.05 - default
+    //   0.04 - slower and less aliasing in darks
+    // Special notes when using FXAA_GREEN_AS_LUMA,
+    //   Likely want to set this to zero.
+    //   As colors that are mostly not-green
+    //   will appear very dark in the green channel!
+    //   Tune by looking at mostly non-green content,
+    //   then start at zero and increase until aliasing is a problem.
+    0.05, // FxaaFloat fxaaConsoleEdgeThresholdMin,
+    //    
+    // Extra constants for 360 FXAA Console only.
+    // Use zeros or anything else for other platforms.
+    // These must be in physical constant registers and NOT immedates.
+    // Immedates will result in compiler un-optimizing.
+    // {xyzw} = float4(1.0, -1.0, 0.25, -0.25)
+    vec4(0.0) // FxaaFloat4 fxaaConsole360ConstDir);
+    );
+}
+
+#endif // FRAGMENT_SHADER

          
A => liminal_legacy/liminal/assets/shaders/ppfx/luma.glsl +18 -0
@@ 0,0 1,18 @@ 
+#include "std/std.glsl"
+#include "copy.glsl"
+
+#if FRAGMENT_SHADER
+
+uniform sampler2D fb_color0;
+
+// CCIR 601
+const vec3 gray = vec3(0.299, 0.587, 0.114);
+// Rec. 709
+//const vec3 gray = vec3(0.2126, 0.7152, 0.0722);
+
+void main() {
+    vec3 color = texture(fb_color0, absolute_texcoord).rgb;
+    out_Color.r = dot(color, gray);
+}
+
+#endif
  No newline at end of file

          
A => liminal_legacy/liminal/assets/shaders/ppfx/oculus_rift.glsl +63 -0
@@ 0,0 1,63 @@ 
+
+#include "std/std.glsl"
+#include "copy.glsl"
+
+#if FRAGMENT_SHADER
+
+uniform sampler2D fb_color0;
+
+uniform vec2 LensCenter;
+uniform vec2 ScreenCenter;
+uniform vec2 Scale;
+uniform vec2 ScaleIn;
+uniform vec4 HmdWarpParam;
+uniform vec4 ChromAbParam;
+
+// Scales input texture coordinates for distortion.
+// ScaleIn maps texture coordinates to Scales to ([-1, 1]), although top/bottom will be
+// larger due to aspect ratio.
+void main()
+{
+   vec2 lensCenter = LensCenter;
+   vec2 screenCenter = ScreenCenter;
+   
+   vec2 oTexCoord = screen_texcoord;
+   
+   float f = (eye_id+1.0)*0.5;
+   lensCenter.x = mix(LensCenter.x, 1.0 - LensCenter.x, f);
+   screenCenter.x = mix(ScreenCenter.x, 1.0 - ScreenCenter.x, f);
+
+   vec2  theta = (oTexCoord - lensCenter) * ScaleIn;
+   float rSq= theta.x * theta.x + theta.y * theta.y;
+   vec2  theta1 = theta * (HmdWarpParam.x + HmdWarpParam.y * rSq + 
+                  HmdWarpParam.z * rSq * rSq + HmdWarpParam.w * rSq * rSq * rSq);
+   
+   // Detect whether blue texture coordinates are out of range since these will scaled out the furthest.
+   vec2 thetaBlue = theta1 * (ChromAbParam.z + ChromAbParam.w * rSq);
+   vec2 tcBlue = lensCenter + Scale * thetaBlue;
+   
+   if (!all(equal(clamp(tcBlue, screenCenter-vec2(0.25,0.5), screenCenter+vec2(0.25,0.5)), tcBlue)))
+   {
+       out_Color = vec4(0.3,0.3,0.3,1.0);
+       return;
+   }
+   
+   // Now do blue texture lookup.
+   float blue = texture(fb_color0, tcBlue).b;
+   
+   // Do green lookup (no scaling).
+   vec2  tcGreen = lensCenter + Scale * theta1;
+   vec4  center = texture(fb_color0, tcGreen);
+   
+   // Do red scale and lookup.
+   vec2  thetaRed = theta1 * (ChromAbParam.x + ChromAbParam.y * rSq);
+   vec2  tcRed = lensCenter + Scale * thetaRed;
+   float red = texture(fb_color0, tcRed).r;
+   
+   out_Color = vec4(red, center.g, blue, 1);
+#if 0
+   out_Color.rgb += vec3(eye_id<0?1.0:0.0, eye_id>0?1.0:0.0, eye_id==0?1.0:0.0);
+#endif
+}
+
+#endif

          
A => liminal_legacy/liminal/assets/shaders/ppfx/ppfx_debug_depth.fs +12 -0
@@ 0,0 1,12 @@ 
+uniform sampler2D fb_color0;
+
+const float stepsize = 100.0;
+
+void main() {
+    vec2 uv = gl_TexCoord[0].st;
+    uv = ((uv - viewport_offset) / viewport_scale);
+
+    float d = mod(texture2D(fb_color0, uv).r * stepsize, 1.0);
+
+    gl_FragColor = vec4(vec3(d), 1.0);
+}
  No newline at end of file

          
A => liminal_legacy/liminal/assets/shaders/ppfx/ppfx_hdr_clip.fs +8 -0
@@ 0,0 1,8 @@ 
+uniform sampler2D fb_color0;
+uniform sampler2D blurfx_color1;
+
+void main() {
+    vec3 color = texture2D(fb_color0, gl_TexCoord[0].st).rgb
+               + texture2D(blurfx_color1, gl_TexCoord[0].st).rgb;
+    gl_FragColor = vec4(color,1.0);
+}

          
A => liminal_legacy/liminal/assets/shaders/ppfx/ppfx_max.fs +7 -0
@@ 0,0 1,7 @@ 
+uniform sampler2D fbA_color0;
+uniform sampler2D fbB_color0;
+
+
+void main() {
+    gl_FragColor = max(texture2D(fbA_color0, gl_TexCoord[0].st), texture2D(fbB_color0, gl_TexCoord[0].st));
+}
  No newline at end of file

          
A => liminal_legacy/liminal/assets/shaders/ppfx/tonemap.glsl +153 -0
@@ 0,0 1,153 @@ 
+
+#include "std/std.glsl"
+#include "copy.glsl"
+
+#ifndef USE_GRADING
+#define USE_GRADING 0
+#endif
+
+#ifndef ANIMATE_GRADING
+#define ANIMATE_GRADING 0
+#endif
+
+#ifndef USE_UV_LUT
+#define USE_UV_LUT 0
+#endif
+
+#ifndef UV_LUT_ABSOLUTE
+#define UV_LUT_ABSOLUTE 0
+#endif
+
+#ifndef USE_DITHER
+#if VIDEO_QUALITY >= VQ_HIGH
+#define USE_DITHER 1
+#else
+#define USE_DITHER 0
+#endif
+#endif
+
+#if FRAGMENT_SHADER
+
+#include "lib/srgb.glsl"
+
+uniform sampler2D fb_color0;
+
+uniform sampler3D lut_grading0;
+#if ANIMATE_GRADING
+uniform sampler3D lut_grading1;
+uniform float lut_grading_mix = 0.0;
+#endif
+
+#if USE_UV_LUT
+uniform sampler2D uv_lut;
+uniform vec2 uv_lut_scale = vec2(1.0);
+#endif
+uniform vec2 fb_size;
+
+const float shoulder_strength = 0.22; // 0.15
+const float linear_strength = 0.30; // 0.5
+const float linear_angle = 0.10;
+const float toe_strength = 0.20;
+const float toe_numerator = 0.01; // 0.02
+const float toe_denominator = 0.30;
+
+uniform float exposure = 2.0;
+
+const float linear_white = 11.2;
+const vec3 gray = vec3(0.299, 0.587, 0.114);
+
+float ff_filmic(float x) {
+    return (
+    (x*(shoulder_strength*x+linear_angle*linear_strength)+toe_strength*toe_numerator)/
+    (x*(shoulder_strength*x+linear_strength)+toe_strength*toe_denominator))
+    - toe_numerator/toe_denominator;
+}
+
+vec3 ff_filmic3(vec3 x) {
+    return (
+    (x*(shoulder_strength*x+linear_angle*linear_strength)+toe_strength*toe_numerator)/
+    (x*(shoulder_strength*x+linear_strength)+toe_strength*toe_denominator))
+    - toe_numerator/toe_denominator;
+}
+
+float ff_filmic_gamma(float linear) {
+    float x = max(0.0, linear-0.004);
+    return (x*(x*6.2+0.5))/(x*(x*6.2+1.7)+0.06);
+}
+
+vec3 ff_filmic_gamma3(vec3 linear) {
+    vec3 x = max(vec3(0.0), linear-0.004);
+    return (x*(x*6.2+0.5))/(x*(x*6.2+1.7)+0.06);
+}
+
+vec3 dither_find_closest(vec2 uv, vec3 c0)
+{
+    vec2 puv = mod(fb_size * uv, 4.0);
+    int x = int(puv.x);
+    int y = int(puv.y);
+    
+    vec4 dither[4];
+
+    dither[0] = vec4( 1.0, 33.0,  9.0, 41.0);
+    dither[1] = vec4(49.0, 17.0, 57.0, 25.0);
+    dither[2] = vec4(13.0, 45.0,  5.0, 37.0);
+    dither[3] = vec4(61.0, 29.0, 53.0, 21.0);
+    
+    float limit = 0.0;
+    limit = (dither[x][y]+1.0)/64.0;
+    
+    return step(vec3(limit), c0);
+}
+
+vec3 dither(vec3 color) {
+    // dither 16 to 8 bit
+    vec3 bit_diff = mod(color*255.0, 1.0);
+    vec3 bit_step = dither_find_closest(screen_texcoord, bit_diff);
+    return color + (bit_step - bit_diff)/255.0;
+}
+
+void main() {
+    vec2 uv = screen_texcoord;
+    
+#if USE_UV_LUT
+#if UV_LUT_ABSOLUTE
+    uv = texture(uv_lut, uv).rg;
+#else
+    uv += (texture(uv_lut, uv).rg*2.0 - 1.0) * uv_lut_scale;
+#endif
+#endif   
+    
+    vec3 color = texture(fb_color0, uv).rgb;
+    
+    // filmic tonemapping
+    color = ff_filmic_gamma3(color * exposure);// / ff_filmic_gamma(linear_white);
+
+    // clamp
+    color = clamp(color, 0.0, 1.0);
+    
+    // gamma
+    // color = lin2srgb(color);
+    
+#if USE_GRADING
+    // final color grading
+#if !ANIMATE_GRADING
+    color = texture(lut_grading0, color).rgb;
+#else    
+    color = mix(
+        texture(lut_grading0, color).rgb,
+        texture(lut_grading1, color).rgb,
+        lut_grading_mix);
+#endif // ANIMATE_GRADING
+#endif // USE_GRADING
+
+    float luma = dot(color, gray);
+
+#if USE_DITHER
+    // dither 16 to 8bit
+    color = dither(color);
+#endif
+
+    // store luma in alpha channel
+    out_Color = vec4(color, luma);
+}
+#endif
  No newline at end of file

          
A => liminal_legacy/liminal/assets/shaders/profiler/simple.glsl +20 -0
@@ 0,0 1,20 @@ 
+#include "std/std.glsl"
+
+varying vec4 color;
+
+#if VERTEX_SHADER
+
+void main(void)
+{
+   // transformed position
+   gl_Position = mtx_proj * (mtx_view * (mtx_model * in_Position));
+   color = in_Color;
+}
+
+#elif FRAGMENT_SHADER
+
+void main() {
+    out_Color = color;
+}
+
+#endif
  No newline at end of file

          
A => liminal_legacy/liminal/assets/shaders/sm/font_shadow.fs +11 -0
@@ 0,0 1,11 @@ 
+
+#include "sm/shadow_frag.glsl"
+#include "font_frag.glsl"
+
+void main(void)
+{
+    float d = read_font_alpha(gl_TexCoord[0]);
+    if (d <= 0.5)
+        discard;
+    write_shadow_frag();
+}

          
A => liminal_legacy/liminal/assets/shaders/sm/font_shadow.vs +9 -0
@@ 0,0 1,9 @@ 
+
+#include "sm/shadow_vars.glsl"
+#include "font_vert.glsl"
+
+void main(void)
+{
+    gl_Position = ftransform();
+    write_font_attribs();
+}

          
A => liminal_legacy/liminal/assets/shaders/sm/ppfx/blur_evsm.glsl +28 -0
@@ 0,0 1,28 @@ 
+#include "std/std.glsl"
+#include "ppfx/blit_layers.glsl"
+
+#if FRAGMENT_SHADER
+
+DATA_QUAL in BlitLayerData data;
+
+uniform sampler2DArray fb_color0;
+uniform vec2 fb_texel;
+uniform vec2 fb_size;
+
+vec4 sample_blur_texture(vec2 uv, vec2 offset) {
+    return texture(fb_color0, vec3(uv + offset, data.layer));
+}
+
+#include "lib/blur.glsl"
+
+void main() {
+    vec2 uv = gl_FragCoord.xy * fb_texel;
+    if (data.layer == LAYER_INIT) {
+        out_Color = blur_texture_fastgauss_5x5(uv, fb_texel, fb_size);
+    } else {
+        out_Color = blur_texture_fastgauss_3x3(uv, fb_texel, fb_size);
+    }
+        //out_Color = texelFetch(fb_color0, ivec3(gl_FragCoord.xy, layer), 0);
+}
+
+#endif
  No newline at end of file

          
A => liminal_legacy/liminal/assets/shaders/sm/ppfx/convert_evsm.glsl +90 -0
@@ 0,0 1,90 @@ 
+#include "std/std.glsl"
+#define INT_LAYER 1
+#include "ppfx/blit_layers.glsl"
+
+#if FRAGMENT_SHADER
+
+DATA_QUAL in BlitLayerData data;
+
+#include "lib/evsm.glsl"
+
+#if MSAA
+uniform sampler2DMSArray fb_depth;
+#else
+uniform sampler2DArray fb_depth;
+#endif
+
+#define FETCH_EVSM_SAMPLE(X) \
+    convert_depth(texelFetch(fb_depth, \
+        ivec3(gl_FragCoord.xy, data.layer), X).r)
+
+void main() {
+#if MSAA == 16
+    vec4
+    average  = FETCH_EVSM_SAMPLE(0);
+    average += FETCH_EVSM_SAMPLE(1);
+    average += FETCH_EVSM_SAMPLE(2);
+    average += FETCH_EVSM_SAMPLE(3);
+    average += FETCH_EVSM_SAMPLE(4);
+    average += FETCH_EVSM_SAMPLE(5);
+    average += FETCH_EVSM_SAMPLE(6);
+    average += FETCH_EVSM_SAMPLE(7);
+    average += FETCH_EVSM_SAMPLE(8);
+    average += FETCH_EVSM_SAMPLE(9);
+    average += FETCH_EVSM_SAMPLE(10);
+    average += FETCH_EVSM_SAMPLE(11);
+    average += FETCH_EVSM_SAMPLE(12);
+    average += FETCH_EVSM_SAMPLE(13);
+    average += FETCH_EVSM_SAMPLE(14);
+    average += FETCH_EVSM_SAMPLE(15);
+    out_Color = average * 0.0625;
+#elif MSAA == 12
+    vec4
+    average  = FETCH_EVSM_SAMPLE(0);
+    average += FETCH_EVSM_SAMPLE(1);
+    average += FETCH_EVSM_SAMPLE(2);
+    average += FETCH_EVSM_SAMPLE(3);
+    average += FETCH_EVSM_SAMPLE(4);
+    average += FETCH_EVSM_SAMPLE(5);
+    average += FETCH_EVSM_SAMPLE(6);
+    average += FETCH_EVSM_SAMPLE(7);
+    average += FETCH_EVSM_SAMPLE(8);
+    average += FETCH_EVSM_SAMPLE(9);
+    average += FETCH_EVSM_SAMPLE(10);
+    average += FETCH_EVSM_SAMPLE(11);
+    out_Color = average / 12.0;
+#elif MSAA == 8
+    vec4
+    average  = FETCH_EVSM_SAMPLE(0);
+    average += FETCH_EVSM_SAMPLE(1);
+    average += FETCH_EVSM_SAMPLE(2);
+    average += FETCH_EVSM_SAMPLE(3);
+    average += FETCH_EVSM_SAMPLE(4);
+    average += FETCH_EVSM_SAMPLE(5);
+    average += FETCH_EVSM_SAMPLE(6);
+    average += FETCH_EVSM_SAMPLE(7);
+    out_Color = average * 0.125;
+#elif MSAA == 4
+    vec4
+    average  = FETCH_EVSM_SAMPLE(0);
+    average += FETCH_EVSM_SAMPLE(1);
+    average += FETCH_EVSM_SAMPLE(2);
+    average += FETCH_EVSM_SAMPLE(3);
+    out_Color = average * 0.25;
+#elif MSAA == 2
+    vec4
+    average  = FETCH_EVSM_SAMPLE(0) + FETCH_EVSM_SAMPLE(1);
+    out_Color = average * 0.5;
+#elif MSAA
+    float weight = 1.0 / float(MSAA);
+    vec4 average = vec4(0.0);
+    for (int i = 0; i < MSAA; ++i) {
+        average += weight * FETCH_EVSM_SAMPLE(i);
+    }
+    out_Color = average;
+#else
+    out_Color = FETCH_EVSM_SAMPLE(0);
+#endif
+}
+
+#endif

          
A => liminal_legacy/liminal/assets/shaders/sm/shadow.glsl +40 -0
@@ 0,0 1,40 @@ 
+
+#include "std/std.glsl"
+
+#if VERTEX_SHADER
+
+void main(void)
+{
+    gl_Position = mtx_proj * (mtx_view * (mtx_model * vec4(in_Position.xyz,1.0)));
+}
+
+#elif GEOMETRY_SHADER
+
+layout(triangles) in;
+layout(triangle_strip, max_vertices = LAYER_GS_VERTEX_COUNT) out;
+
+uniform vec4 dm_offsets[LAYER_COUNT];
+
+void main() {
+  for (int k = 0; k < LAYER_COUNT; ++k) {
+        for(int i = 0; i < 3; ++i) {
+            gl_Layer = k;
+            vec4 p = gl_in[i].gl_Position;
+            vec4 o = dm_offsets[k];
+            gl_Position = vec4(p.xy * o.xy + o.zw, p.zw);
+            EmitVertex();
+        }
+        EndPrimitive();
+  }
+}
+
+#elif FRAGMENT_SHADER
+
+#include "sm/shadow_frag.glsl"
+
+void main(void)
+{
+    write_shadow_frag();
+}
+
+#endif
  No newline at end of file

          
A => liminal_legacy/liminal/assets/shaders/sm/shadow_frag.glsl +4 -0
@@ 0,0 1,4 @@ 
+
+void write_shadow_frag() {
+    out_Color = vec4( 1.0 );    
+}

          
A => liminal_legacy/liminal/assets/shaders/std/attrib.glsl +17 -0
@@ 0,0 1,17 @@ 
+
+#if VERTEX_SHADER
+in vec4 in_Position;
+in vec3 in_Normal;
+in vec4 in_Color;
+in vec4 in_Color2;
+in vec4 in_Color3;
+in vec4 in_Color4;
+in vec2 in_TexCoord0;
+in vec3 in_TexCoord1;
+in vec3 in_Tangent;
+in vec3 in_Bitangent;
+in vec3 in_Origin;
+in vec3 in_Velocity;
+#elif FRAGMENT_SHADER
+out vec4 out_Color;
+#endif

          
A => liminal_legacy/liminal/assets/shaders/std/compat.glsl +32 -0
@@ 0,0 1,32 @@ 
+#define texture1D texture
+#define texture2D texture
+#define texture3D texture
+#define textureCube texture
+#define texture1DLod textureLod        
+#define texture2DLod textureLod        
+#define texture3DLod textureLod        
+#define textureCubeLod textureLod
+#define texture1DProj textureProj
+#define texture2DProj textureProj
+#define texture3DProj textureProj
+#define shadow2DProj custom_shadow2DProj
+
+#define gl_Vertex in_Position
+#define gl_Normal in_Normal
+#define gl_Color in_Color
+#define gl_MultiTexCoord0 in_TexCoord0
+#define gl_TexCoord inout_TexCoord
+#define gl_FrontColor inout_VertexColor
+#define ftransform custom_ftransform
+#define gl_ClipVertex out_ClipVertex
+
+#define gl_FragColor out_Color
+#define gl_Color inout_VertexColor
+#define gl_TexCoord inout_TexCoord
+#define gl_LightSource u_lightsource
+#define textureQueryLod custom_textureQueryLod
+#define textureQueryLOD custom_textureQueryLod
+
+vec4 custom_ftransform() {
+    return mtx_proj * (mtx_view * (mtx_model * in_Position));
+}

          
A => liminal_legacy/liminal/assets/shaders/std/std.glsl +5 -0
@@ 0,0 1,5 @@ 
+
+#include "std/vq.glsl"
+#include "std/ubo.glsl"
+#include "std/attrib.glsl"
+#include "std/varying.glsl"

          
A => liminal_legacy/liminal/assets/shaders/std/ubo.glsl +32 -0
@@ 0,0 1,32 @@ 
+
+layout(std140) uniform GlobalAttribs {
+    float time;
+};
+
+layout(std140) uniform CameraAttribs {
+    mat4 mtx_view;
+    mat4 mtx_camera;
+    mat4 mtx_proj;
+    mat4 mtx_viewproj;
+    mat4 mtx_inv_proj;
+    mat4 mtx_last_vp;
+    float near;
+    float far;
+};
+
+layout(std140) uniform ViewportAttribs {
+    vec2 viewport_offset;
+    vec2 viewport_scale;
+    float eye_id;
+};
+
+layout(std140) uniform ModelAttribs {
+    mat4 mtx_model;
+    mat4 mtx_last_model;
+    mat4 mtx_inv_model;
+};
+
+// model-view-projection matrix of last frame
+mat4 mtx_last_mvp() {
+    return mtx_last_vp * mtx_last_model;
+}

          
A => liminal_legacy/liminal/assets/shaders/std/varying.glsl +6 -0
@@ 0,0 1,6 @@ 
+
+#if VERTEX_SHADER
+#define varying out
+#elif FRAGMENT_SHADER
+#define varying in
+#endif // FRAGMENT_SHADER
  No newline at end of file

          
A => liminal_legacy/liminal/assets/shaders/std/vq.glsl +7 -0
@@ 0,0 1,7 @@ 
+#define VQ_HIGHEST 6  
+#define VQ_HIGHER 5 
+#define VQ_HIGH 4 
+#define VQ_MEDIUM 3 
+#define VQ_LOW 2 
+#define VQ_LOWER 1 
+#define VQ_LOWEST 0
  No newline at end of file

          
A => liminal_legacy/liminal/assets/shaders/ui2d/ninepatch2d.glsl +45 -0
@@ 0,0 1,45 @@ 
+
+#include "std/std.glsl"
+#include "ui2d/ui2d.glsl"
+
+varying vec2 texcoord;
+
+#if VERTEX_SHADER
+
+// x1, x2, y1, y2 (x0, y0 and x3,y3 are implicit (0..1))
+uniform ivec4 grid = ivec4(0, 0, 0, 0); 
+uniform ivec2 gridsize = ivec2(0, 0);
+
+vec2 pixel2uv(vec2 p) {
+    p =  p / vec2(gridsize);
+    //p.y = 1.0 - p.y;
+    return p;
+}
+
+void main(void) {
+    vec2 p0 = vec2(rectangle.xy);
+    vec2 p1 = vec2(grid.xy);
+    vec2 p3 = vec2(grid.zw);
+    vec2 p2 = vec2(rectangle.zw) - (p1 + p3);
+    
+    vec2 tc1 = vec2(grid.xy);
+    vec2 tc3 = vec2(grid.zw);
+    vec2 tc2 = vec2(gridsize) - (tc1 + tc3);
+
+    vec2 v = in_Position.xy;
+    vec2 w0 = clamp(v, 0.0, 1.0);
+    vec2 w1 = clamp(v - 1.0, 0.0, 1.0);
+    vec2 w2 = clamp(v - 2.0, 0.0, 1.0);
+    
+    gl_Position = mtx_proj * (mtx_view * (mtx_model * vec4(pixel2unit(
+        p0 + p1 * w0 + p2 * w1 + p3 * w2), 0.0, 1.0)));
+    texcoord = pixel2uv(tc1 * w0 + tc2 * w1 + tc3 * w2);
+}
+
+#elif FRAGMENT_SHADER
+
+void main() {
+    out_Color = texture(smp_texture, texcoord);
+}
+
+#endif
  No newline at end of file

          
A => liminal_legacy/liminal/assets/shaders/ui2d/quad2d.glsl +26 -0
@@ 0,0 1,26 @@ 
+
+#include "std/std.glsl"
+
+#include "ui2d/ui2d.glsl"
+
+varying vec2 texcoord;
+
+#if VERTEX_SHADER
+
+void main(void) {
+    gl_Position = mtx_proj * (mtx_view * (mtx_model * vec4(
+     pixel2unit(rectangle.xy + rectangle.zw*in_Position.xy), 0.0, 1.0)));
+    texcoord = in_TexCoord0;
+}
+
+#elif FRAGMENT_SHADER
+
+uniform vec4 uvrect = vec4(0.0, 0.0, 1.0, 1.0);
+uniform vec4 color = vec4(1.0,1.0,1.0,1.0);
+
+void main() {
+    vec2 uv = uvrect.xy + texcoord * uvrect.zw;
+    out_Color = texture(smp_texture, uv) * color;
+}
+
+#endif
  No newline at end of file

          
A => liminal_legacy/liminal/assets/shaders/ui2d/ui2d.glsl +12 -0
@@ 0,0 1,12 @@ 
+
+uniform vec2 screensize = vec2(800.0, 600.0);
+// x, y, width, height
+uniform ivec4 rectangle = ivec4(0, 0, 16, 16);
+uniform sampler2D smp_texture;
+
+vec2 pixel2unit(vec2 p) {
+    p =  (p / screensize)*2.0 - 1.0;
+    p.y *= -1.0;
+    return p;
+}
+

          
A => liminal_legacy/liminal/assets/textures/blank.png +0 -0

        
A => liminal_legacy/liminal/assets/textures/blender_icons16.png +0 -0

        
A => liminal_legacy/liminal/assets/textures/lut_default.png +0 -0

        
A => liminal_legacy/liminal/assets/textures/spot.png +0 -0

        
A => liminal_legacy/liminal/assets/textures/zero.png +0 -0

        
A => liminal_legacy/liminal/blenderclient/__init__.py +386 -0
@@ 0,0 1,386 @@ 
+
+import os
+import sys
+import logging
+import weakref
+
+import bl_ui #@UnresolvedImport
+import bpy #@UnresolvedImport
+import mathutils #@UnresolvedImport
+# see http://www.blender.org/documentation/blender_python_api_2_63_0/bpy.app.handlers.html
+# for more infos
+from bpy.app.handlers import persistent #@UnresolvedImport
+
+from .structure import BlenderClient
+
+################################################################################
+
+__all__ = []
+
+################################################################################
+
+BEIGE_FILE_EXTENSION = '.blendc'
+
+#bpy.app.debug_events = True
+log = logging.getLogger('liminal.blender')
+
+################################################################################
+
+class BeigeRender(bpy.types.RenderEngine):
+    bl_idname = 'BEIGE_RENDER'
+    bl_label = "Beige"
+
+BLENDER_PANEL_BLACKLIST = {
+    'RENDER_PT_embedded',
+    'RENDER_PT_game_player',
+    'RENDER_PT_game_stereo',
+    'RENDER_PT_game_shading',
+}
+
+def augment_props(props):
+    # add liminal to all game engine panels
+    for key,value in props.__dict__.items():
+        if not hasattr(value, 'COMPAT_ENGINES'):
+            continue
+        if key in BLENDER_PANEL_BLACKLIST:
+            continue
+        if 'BLENDER_GAME' in value.COMPAT_ENGINES:
+            #print('"{0}",'.format(key))
+            value.COMPAT_ENGINES.add('BEIGE_RENDER') 
+
+augment_props(bl_ui.properties_game)
+augment_props(bl_ui.properties_particle)
+augment_props(bl_ui.properties_material)
+augment_props(bl_ui.properties_data_mesh)
+augment_props(bl_ui.properties_texture)
+augment_props(bl_ui.properties_data_camera)
+augment_props(bl_ui.properties_data_lamp)
+augment_props(bl_ui.properties_world)
+augment_props(bl_ui.properties_scene)
+augment_props(bl_ui.properties_render)
+
+# liminal related properties
+################################################################################
+
+# new RNA props for scene
+class BeigeScene(bpy.types.PropertyGroup):
+    scenepass = bpy.props.IntProperty(
+        name="Scene Pass", 
+        description = "Order priority / rank when rendering multiple scenes",
+        min = 0, max = 1000,
+        default = 0)
+    archive_path = bpy.props.StringProperty(
+        name="Archive",
+        description = "Where to export the blend file to",
+        default = "//data" + BEIGE_FILE_EXTENSION,
+        subtype = "FILE_PATH")
+    auto_export = bpy.props.BoolProperty(
+        name="Export on Start",
+        description = "Automatically update archive when starting player",
+        default = True)
+    fullscreen = bpy.props.BoolProperty(
+        name="Fullscreen",
+        description = "Start game in fullscreen mode",
+        default = False)
+    loglevel = bpy.props.EnumProperty(
+        items = [
+            ('debug', 'Debug', 'Include internal debugging messages'),
+            ('info', 'Info', 'Include informing messages'),
+            ('warn', 'Warn', 'Include warning messages'),
+            ('error', 'Error', 'Include error messages'),
+            ('fatal', 'Fatal', 'Print only fatal messages'),
+        ],
+        name="Loglevel",
+        description = "Loglevel of Engine when running game",
+        default = 'warn')
+    msaa = bpy.props.EnumProperty(
+        items = [
+            ('0', 'None', 'No anti-aliasing'),
+            ('2', '2x', '2x MSAA'),
+            ('4', '4x', '4x MSAA'),
+        ],
+        name="MSAA Level",
+        description = "Anti-aliasing level",
+        default = '0')
+
+bpy.utils.register_class(BeigeScene)
+bpy.types.Scene.liminal_settings = bpy.props.PointerProperty(type=BeigeScene)
+
+################################################################################
+
+class BeigeObject(bpy.types.PropertyGroup):
+    python_class = bpy.props.StringProperty(
+        name="Python Class",
+        description = "Actor class to instantiate (syntax: module.class)",
+        default = "")
+
+bpy.utils.register_class(BeigeObject)
+bpy.types.Object.liminal_settings = bpy.props.PointerProperty(type=BeigeObject)
+
+################################################################################
+
+class BeigeMaterial(bpy.types.PropertyGroup):
+    cutout = bpy.props.BoolProperty(
+        name="Cutout", 
+        description = "Only render to Z-Buffer in Game Engine",
+        default = False)
+
+bpy.utils.register_class(BeigeMaterial)
+bpy.types.Material.liminal_settings = bpy.props.PointerProperty(type=BeigeMaterial)
+
+################################################################################
+
+class BeigePanel():
+    COMPAT_ENGINES = {'BEIGE_RENDER'}
+    
+    @classmethod
+    def poll(cls, context):
+        rd = context.scene.render
+        return (rd.engine in cls.COMPAT_ENGINES)
+
+class BeigeScenePanel(BeigePanel, bpy.types.Panel):
+    bl_space_type = "PROPERTIES"
+    bl_region_type = "WINDOW"
+    bl_context = "render"
+    bl_label = "Beige"
+    
+    def draw(self, context):
+        layout = self.layout
+
+        scene = context.scene
+        bs = scene.liminal_settings
+        
+        row = layout.row()
+        
+        col = row.column()
+        col.operator("view3d.liminal_start_player", text="Start")
+        col.operator("view3d.liminal_export", text="Export")
+        
+        col = row.column()        
+        col.prop(bs, "auto_export")
+        col.prop(bs, "fullscreen")
+        col.prop(bs, "loglevel")
+        col.prop(bs, "msaa")
+        
+        row = layout.row()
+        row.prop(bs, "archive_path")
+        
+        if 0:
+            row = layout.row()
+            if BlenderClient.instance:
+                button_label = "Disconnect"
+                count = BlenderClient.instance.get_todo_list_count() #@UndefinedVariable
+                if count > 0:
+                    label = "Updating {0} items".format(count)
+                else:
+                    label = "Connected"
+            else:
+                button_label = "Connect"
+                label = "Not connected"
+            row.operator("view3d.liminal_connect", text=button_label)
+            row.label(label)
+        
+        row = layout.row()
+        row.prop(bs, "scenepass")
+
+################################################################################
+
+class BeigeObjectPanel(BeigePanel, bpy.types.Panel):
+    bl_space_type = "PROPERTIES"
+    bl_region_type = "WINDOW"
+    bl_context = "object"
+    bl_label = "Beige Object"
+
+    @classmethod
+    def poll(cls, context):
+        return (not (context.object is None)) and BeigePanel.poll(context)
+    
+    def draw(self, context):
+        layout = self.layout
+
+        obj = context.object
+        bm = obj.liminal_settings
+        
+        row = layout.row()
+        row.prop(bm, "python_class")
+
+################################################################################
+        
+class BeigeMaterialPanel(BeigePanel, bpy.types.Panel):
+    bl_space_type = "PROPERTIES"
+    bl_region_type = "WINDOW"
+    bl_context = "material"
+    bl_label = "Beige Material"
+ 
+    @classmethod
+    def poll(cls, context):
+        return (not (context.material is None)) and BeigePanel.poll(context)
+    
+    def draw(self, context):
+        layout = self.layout
+
+        mat = context.material
+        bm = mat.liminal_settings
+        
+        row = layout.row(align=False)
+        row.prop(bm, "cutout")
+
+################################################################################
+
+def find_bin_path(bin_name):
+    if sys.platform == 'win32':
+        bin_name = bin_name + '.exe'
+    # find pypy in path
+    PATH = os.environ['PATH'].split(os.pathsep)
+    for dirpath in PATH:
+        fullpath = os.path.join(dirpath, bin_name)
+        if os.path.isfile(fullpath):
+            return fullpath
+
+def get_liminal_archive_path():
+    return bpy.path.abspath(bpy.context.scene.liminal_settings.archive_path)
+
+def launch_player():
+    #BlenderClient.start()
+    bs = bpy.context.scene.liminal_settings
+    archive_path = get_liminal_archive_path()
+    if bs.auto_export:
+        liminal_export()
+    msaa = bs.msaa
+    llevel = bs.loglevel 
+    liminal_path = find_bin_path('liminal')
+    assert liminal_path, 'liminal not found in PATH'
+    args = [liminal_path,
+        '--loglevel',llevel,
+        '-a',archive_path,
+        '--msaa',msaa,
+        '--scene',bpy.context.scene.name]
+    if bs.fullscreen:
+        args += ['--fullscreen']
+    print('Launching {}...'.format(' '.join(args)))
+    pid = os.spawnv(os.P_NOWAIT, liminal_path, args)
+    
+class LaunchPlayerOperator(bpy.types.Operator):
+    '''Start Beige player'''
+    bl_idname = "view3d.liminal_start_player"
+    bl_label = "Start Player"
+
+    @classmethod
+    def poll(cls, context):
+        return context.scene.render.engine == 'BEIGE_RENDER'
+
+    def execute(self, context):
+        launch_player()
+        return {'FINISHED'}
+
+################################################################################
+
+def liminal_export():
+    archive_path = get_liminal_archive_path()
+    BlenderClient.export(archive_path)
+    
+class ExportOperator(bpy.types.Operator):
+    '''Export Beige archive'''
+    bl_idname = "view3d.liminal_export"
+    bl_label = "Export Beige Archive"
+
+    @classmethod
+    def poll(cls, context):
+        return context.scene.render.engine == 'BEIGE_RENDER'
+
+    def execute(self, context):
+        liminal_export()
+        return {'FINISHED'}
+
+class FileExportOperator(bpy.types.Operator):
+    '''Export Beige archive'''
+    bl_idname = "export.liminalblob"
+    bl_label = "Export Beige Archive"
+    filepath = bpy.props.StringProperty(subtype='FILE_PATH')
+    stamp = bpy.props.StringProperty()
+    
+    def execute(self, context):
+        filepath = bpy.path.ensure_ext(self.filepath, BEIGE_FILE_EXTENSION)
+        BlenderClient.export(filepath, stamp=self.stamp)
+        return {'FINISHED'}
+
+    def invoke(self, context, event):
+        if not self.filepath:
+            self.filepath = bpy.path.ensure_ext(bpy.data.filepath, 
+                BEIGE_FILE_EXTENSION)
+        WindowManager = context.window_manager
+        WindowManager.fileselect_add(self)
+        return {'RUNNING_MODAL'}
+        
+def export_menu_func(self, context):
+    self.layout.operator(FileExportOperator.bl_idname, 
+        text="Beige Archive ({})".format(BEIGE_FILE_EXTENSION))
+            
+################################################################################
+    
+class ToggleBeigeOperator(bpy.types.Operator):
+    '''Connect to Beige data server'''
+    bl_idname = "view3d.liminal_connect"
+    bl_label = "Connect to Beige"
+
+    @classmethod
+    def poll(cls, context):
+        return context.scene.render.engine == 'BEIGE_RENDER'
+
+    def execute(self, context):
+        BlenderClient.startstop()
+        return {'FINISHED'}
+    
+################################################################################
+
+def fix_old_props():
+    return
+    for scene in bpy.data.scenes:
+        if hasattr(scene.game_settings,'scenepass'):
+            sp = getattr(scene.game_settings,'scenepass')
+            print('translating scenepass',sp)
+            scene.liminal_settings.scenepass = sp
+    for mat in bpy.data.materials:
+        if hasattr(mat.game_settings,'cutout'):
+            co = getattr(mat.game_settings,'cutout')
+            print('translating cutout',co)
+            mat.liminal_settings.cutout = co
+
+################################################################################
+
+@persistent
+def load_post(ctx):
+    fix_old_props()
+
+@persistent
+def scene_update_post(scene):
+    BlenderClient.update_post()
+
+################################################################################
+
+classes = [
+    BeigeRender,
+    BeigeScenePanel,
+    BeigeObjectPanel,
+    BeigeMaterialPanel,
+    LaunchPlayerOperator,
+    ExportOperator,
+    FileExportOperator,
+    ToggleBeigeOperator,
+]
+
+def register():
+    logging.basicConfig(level=logging.INFO)
+    for cls in classes:
+        bpy.utils.register_class(cls)
+    #bpy.app.handlers.load_post.append(load_post)
+    bpy.app.handlers.scene_update_post.append(scene_update_post)
+    bpy.types.INFO_MT_file_export.append(export_menu_func)
+    fix_old_props()
+ 
+def unregister():
+    bpy.types.INFO_MT_file_export.remove(export_menu_func)
+    for cls in classes:
+        bpy.utils.unregister_class(cls)
+
+################################################################################

          
A => liminal_legacy/liminal/blenderclient/structure.py +1122 -0
@@ 0,0 1,1122 @@ 
+
+import time
+import os
+import hashlib
+import binascii
+import sys
+import traceback
+import logging
+
+import bpy #@UnresolvedImport
+import gpu #@UnresolvedImport
+
+#from ..data.client import DataClient
+from ..data.source import DataArchive
+from ..extras.purify import *
+
+################################################################################
+
+log = logging.getLogger('liminal.blender')
+
+################################################################################
+
+def layers_to_flags(layers):
+    flags = 0
+    for i,layer in enumerate(layers):
+        if layer:
+            flags |= (1<<i)
+    return flags 
+
+def items_to_names(items):
+    return [item.name for item in items]
+
+def matrix_to_list(mtx):
+    return [list(vec) for vec in mtx]
+
+def dirhash(path):
+    dirhash = hashlib.md5(
+        path.encode('utf-8')).hexdigest()
+    return dirhash[:8]
+
+def get_libpath(library):
+    if library:
+        filepath = library.filepath
+    else:
+        filepath = os.path.normpath(os.path.abspath(bpy.data.filepath))
+    return filepath
+
+def get_libid(library):
+    return dirhash(get_libpath(library))
+    
+def resolve_libid(obj):
+    return get_libid(obj.library)
+
+def resolve_image_path(img):
+    return bpy.path.relpath(os.path.normpath(
+        bpy.path.abspath(img.filepath,
+        library = img.library)))
+
+def extract_material_shader(material):
+    shader = gpu.export_shader(bpy.context.scene, material)
+    for uniform in shader['uniforms']:
+        uniform.pop('lamp', None)
+        if 'image' in uniform:
+            uniform['image'] = resolve_image_path(uniform['image'])
+        if 'texpixels' in uniform:
+            uniform['texpixels'] = binascii.b2a_qp(
+                uniform['texpixels']).decode('utf-8')
+    return shader
+
+################################################################################
+
+def Layers():
+    return Auto(pack=layers_to_flags)
+
+def AutoRef():
+    return Auto(pack=lambda v:reference(v,uri_only=True))    
+
+def AutoRefList():
+    return Auto(pack=lambda v:reference_list(v,uri_only=True))    
+
+class FromLib(AutoClass):
+    @classmethod
+    def _on_purify(cls, value):
+        pass
+    
+    @classmethod
+    def _reference(cls, value, **kargs):
+        cl = BlenderClient.instance
+        uri = cl.get_objroot(value) + '/' + cls._group + '/' + value.name
+        if kargs.get('uri_only', False):
+            return uri
+        if not cl.endp.hasitem(uri):
+            if kargs.get('immediate',False):
+                cl.commit(cls, uri, value)
+            else: 
+                cl._todo_list[uri] = (cls, value)
+        return uri
+
+    library = Annotate(resolve_libid)
+    name = Auto()
+
+class Camera(FromLib):
+    _cls = bpy.types.Camera
+    _group = 'cameras'
+    _constants = dict(
+        type = 'camera'
+    )
+        
+    angle_x = Auto()
+    angle_y = Auto()
+    angle = Auto()
+    clip_start = Auto()
+    clip_end = Auto()
+    sensor_width = Auto() 
+    sensor_height = Auto()
+    ortho_scale = Auto()
+    sensor_fit = Auto()
+    shift_x = Auto()
+    shift_y = Auto()
+    mode = AutoAttr.o.type()  
+    
+class Scene(FromLib):
+    _cls = bpy.types.Scene
+    _group = 'scenes'
+    _constants = dict(
+        type = 'scene',
+    )
+    
+    object_bases = Auto()
+    camera = AutoPointer()
+    layers = Layers()
+    background_set = AutoRef()
+    scenepass = AutoAttr.o.liminal_settings.scenepass()
+    exit_key = AutoAttr.o.game_settings.exit_key()
+    fps = AutoAttr.o.game_settings.fps()
+    logic_step_max = AutoAttr.o.game_settings.logic_step_max()
+    obstacle_simulation = AutoAttr.o.game_settings.obstacle_simulation()
+    occlusion_culling_resolution = AutoAttr.o.game_settings.occlusion_culling_resolution()
+    physics_engine = AutoAttr.o.game_settings.physics_engine()
+    physics_gravity = AutoAttr.o.game_settings.physics_gravity()
+    physics_step_max = AutoAttr.o.game_settings.physics_step_max()
+    physics_step_sub = AutoAttr.o.game_settings.physics_step_sub()
+    show_debug_properties = AutoAttr.o.game_settings.show_debug_properties()
+    show_framerate_profile = AutoAttr.o.game_settings.show_framerate_profile()
+    use_activity_culling = AutoAttr.o.game_settings.use_activity_culling()
+    use_occlusion_culling = AutoAttr.o.game_settings.use_occlusion_culling()
+
+class ObjectBase(AutoClass):
+    _cls = bpy.types.ObjectBase
+    _constants = dict(
+        type = 'object_base',
+    )
+    
+    layers = Layers()
+    object = AutoPointer()
+    
+def get_dupli_list(obj, attr):
+    dupli_list = []
+    if obj.dupli_type == 'GROUP':
+        obj.dupli_list_create(bpy.context.scene)
+        for dup in obj.dupli_list:
+            reference(dup.object, immediate=True)
+            dupli_list.append(dup)
+        obj.dupli_list_clear()
+        
+    return dupli_list
+    
+class Group(FromLib):
+    _cls = bpy.types.Group
+    _group = 'groups'
+    _constants = dict(
+        type = 'group',
+    )
+    
+    dupli_offset = Auto()
+    layers = Layers()
+    objects = AutoPointerList()
+    
+class Object(FromLib):
+    _cls = bpy.types.Object
+    _group = 'objects'
+    _constants = dict(
+        type = 'object',
+    )
+    
+    data = AutoPointer()
+    hide = Auto()
+    hide_render = Auto()
+    matrix_basis = Auto()
+    matrix_local = Auto()
+    matrix_parent_inverse = Auto()
+    matrix_world = Auto()
+    dupli_group = AutoPointer()
+    #dupli_list = Auto(resolve=get_dupli_list)
+    actuators = AutoAttr.o.game.actuators()
+    sensors = AutoAttr.o.game.sensors()
+    controllers = AutoAttr.o.game.controllers()
+    game_properties = AutoAttr.o.game.properties()
+    children = AutoRefList()
+    physics_type = AutoAttr.o.game.physics_type()
+    use_actor = AutoAttr.o.game.use_actor()
+    use_ghost = AutoAttr.o.game.use_ghost()
+    radius = AutoAttr.o.game.radius()
+    use_collision_bounds = AutoAttr.o.game.use_collision_bounds()
+    collision_bounds_type = AutoAttr.o.game.collision_bounds_type()
+    collision_margin = AutoAttr.o.game.collision_margin()
+    use_collision_compound = AutoAttr.o.game.use_collision_compound()
+    mass = AutoAttr.o.game.mass()
+    form_factor = AutoAttr.o.game.form_factor()
+    velocity_min = AutoAttr.o.game.velocity_min()
+    velocity_max = AutoAttr.o.game.velocity_max()
+    damping = AutoAttr.o.game.damping()
+    rotation_damping = AutoAttr.o.game.rotation_damping()
+    use_sleep = AutoAttr.o.game.use_sleep()
+    use_rotate_from_normal = AutoAttr.o.game.use_rotate_from_normal()
+    use_material_physics_fh = AutoAttr.o.game.use_material_physics_fh()
+    python_class = AutoAttr.o.liminal_settings.python_class()
+
+#class DupliObject(AutoClass):
+#    _cls = bpy.types.DupliObject
+#    
+#    hide = Auto()
+#    matrix = Auto()
+#    matrix_original = Auto()
+#    object = AutoPointer()
+
+class GameProperty(AutoClass):
+    _cls = bpy.types.GameProperty
+    
+    type = Auto() #@ReservedAssignment
+    name = Auto()
+    show_debug = Auto()
+    value = Auto()
+
+class Mesh(FromLib):
+    _cls = bpy.types.Mesh
+    _group = 'meshes'
+    _constants = dict(
+        type = 'mesh',
+    )
+    
+    @classmethod
+    def _on_purify(cls, value):
+        value.update(calc_tessface=True)
+    
+    vertices = Auto(resolve=lambda o,n:[vert.co for vert in o.vertices])
+    normals = Auto(resolve=lambda o,n:[vert.normal for vert in o.vertices])
+    material_indices = Auto(resolve=lambda o,n:[face.material_index for face in o.tessfaces])
+    faces = Auto(resolve=lambda o,n:[face.vertices for face in o.tessfaces])
+    vertex_colors = AutoAttr.o.tessface_vertex_colors()
+    texcoords = AutoAttr.o.tessface_uv_textures()
+    materials = AutoPointerList()
+
+class MeshColorLayer(AutoClass):
+    _cls = bpy.types.MeshColorLayer
+    _resolve = Convert.o.data()
+
+class MeshColor(AutoClass):
+    _cls = bpy.types.MeshColor
+    _resolve = lambda o: [o.color1,o.color2,o.color3,o.color4]
+
+class MeshTextureFaceLayer(AutoClass):
+    _cls = bpy.types.MeshTextureFaceLayer
+    _resolve = Convert.o.data()
+
+class MeshTextureFace(AutoClass):
+    _cls = bpy.types.MeshTextureFace
+    _resolve = Convert.o.uv()
+
+class Lamp(FromLib):
+    _cls = bpy.types.Lamp
+    _group = 'lamps'
+    _constants = dict(
+        type = 'lamp'
+    )
+
+class Material(FromLib):
+    _cls = bpy.types.Material
+    _group = 'materials'
+    _constants = dict(
+        type = 'material'
+    )
+
+    invisible = AutoAttr.o.game_settings.invisible()
+    use_backface_culling = AutoAttr.o.game_settings.use_backface_culling()
+    use_vertex_color_paint = Auto()
+    diffuse_color = Auto()
+    diffuse_intensity = Auto()
+    shader = Annotate(extract_material_shader)
+    textures = AutoAttr.o.texture_slots()
+    alpha_blend = AutoAttr.o.game_settings.alpha_blend()
+    use_transparency = Auto()
+    alpha = Auto()
+    cutout = AutoAttr.o.liminal_settings.cutout()
+    physics = AutoAttr.o.game_settings.physics()
+    elasticity = AutoAttr.o.physics.elasticity()
+    fh_damping = AutoAttr.o.physics.fh_damping()
+    fh_distance = AutoAttr.o.physics.fh_distance()
+    fh_force = AutoAttr.o.physics.fh_force()
+    friction = AutoAttr.o.physics.friction()
+    use_fh_normal = AutoAttr.o.physics.use_fh_normal()
+
+class MaterialTextureSlot(AutoClass):
+    _cls = bpy.types.MaterialTextureSlot
+    _constants = dict(
+        type = 'texture_slot'
+    )
+    
+    texture = AutoPointer()
+    use_map_color_diffuse = Auto()
+    diffuse_color_factor = Auto()
+    use_map_alpha = Auto()
+    alpha_factor = Auto()
+
+class Texture(FromLib):
+    _cls = bpy.types.Texture
+    _group = 'textures'
+    _constants = dict(
+        type = 'texture'
+    )
+    
+class ImageTexture(Texture):
+    _cls = bpy.types.ImageTexture
+    _constants = dict(
+        type = 'image_texture'
+    )
+    
+    image = AutoPointer()
+    #use_alpha = Auto()
+    use_mipmap = Auto()
+    use_interpolation = Auto()
+
+class Image(FromLib):
+    _cls = bpy.types.Image
+    _group = 'images'
+    _constants = dict(
+        type = 'image'
+    )
+    
+    filepath = Annotate(resolve_image_path)
+    
+class Sensor(AutoClass):
+    #_cls = bpy.types.Sensor
+    
+    # 'controllers',
+    name = Auto()
+    frequency = Auto()
+    invert = Auto()
+    use_level = Auto()
+    use_pulse_false_level = Auto()
+    use_pulse_true_level = Auto()
+    use_tap = Auto()
+    controllers = Auto(pack=items_to_names)
+
+class RaySensor(Sensor):
+    _cls = bpy.types.RaySensor
+    _constants = dict(
+        type = 'ray_sensor'
+    )
+
+    axis = Auto()
+    material = Auto()
+    property = Auto()
+    range = Auto()
+    ray_type = Auto()
+    use_x_ray = Auto()
+
+class PropertySensor(Sensor):
+    _cls = bpy.types.PropertySensor
+    _constants = dict(
+        type = 'property_sensor'
+    )
+    
+    evaluation_type = Auto()
+    property = Auto()
+    value = Auto()
+    value_min = Auto()
+    value_max = Auto()
+    
+
+class MessageSensor(Sensor):
+    _cls = bpy.types.MessageSensor
+    _constants = dict(
+        type = 'message_sensor'
+    )
+    
+    subject = Auto()
+
+class TouchSensor(Sensor):
+    _cls = bpy.types.TouchSensor
+    _constants = dict(
+        type = 'touch_sensor'
+    )
+    
+    material = AutoRef()
+
+class CollisionSensor(Sensor):
+    _cls = bpy.types.CollisionSensor
+    _constants = dict(
+        type = 'collision_sensor'
+    )
+    
+    material = Auto()
+    property = Auto()
+    use_material = Auto()
+    use_pulse = Auto()
+
+class RadarSensor(Sensor):
+    _cls = bpy.types.RadarSensor
+    _constants = dict(
+        type = 'radar_sensor'
+    )
+    
+    angle = Auto()
+    axis = Auto()
+    distance = Auto()
+    property = Auto()
+
+class DelaySensor(Sensor):
+    _cls = bpy.types.DelaySensor
+    _constants = dict(
+        type = 'delay_sensor'
+    )
+    
+    delay = Auto()
+    duration = Auto()
+    use_repeat = Auto()
+
+class RandomSensor(Sensor):
+    _cls = bpy.types.RandomSensor
+    _constants = dict(
+        type = 'random_sensor'
+    )
+    
+    seed = Auto()
+
+class AlwaysSensor(Sensor):
+    _cls = bpy.types.AlwaysSensor
+    _constants = dict(
+        type = 'always_sensor'
+    )
+
+class ActuatorSensor(Sensor):
+    _cls = bpy.types.ActuatorSensor
+    _constants = dict(
+        type = 'actuator_sensor'
+    )
+    
+    actuator = Auto()
+
+class KeyboardSensor(Sensor):
+    _cls = bpy.types.KeyboardSensor
+    _constants = dict(
+        type = 'keyboard_sensor'
+    )
+    
+    key = Auto()
+    log = Auto()
+    modifier_key_1 = Auto()
+    modifier_key_2 = Auto()
+    target = Auto()
+    use_all_keys = Auto()
+
+class JoystickSensor(Sensor):
+    _cls = bpy.types.JoystickSensor
+    _constants = dict(
+        type = 'joystick_sensor'
+    )
+    
+    axis_direction = Auto()
+    axis_number = Auto()
+    axis_threshold = Auto()
+    button_number = Auto()
+    event_type = Auto()
+    hat_direction = Auto()
+    hat_number = Auto()
+    joystick_index = Auto()
+    single_axis_number = Auto()
+    use_all_events = Auto()
+
+class MouseSensor(Sensor):
+    _cls = bpy.types.MouseSensor
+    _constants = dict(
+        type = 'mouse_sensor'
+    )
+    
+    mouse_event = Auto()
+
+class ArmatureSensor(Sensor):
+    _cls = bpy.types.ArmatureSensor
+    _constants = dict(
+        type = 'armature_sensor'
+    )
+    
+    bone = Auto()
+    constraint = Auto()
+    test_type = Auto()
+    value = Auto()
+
+class NearSensor(Sensor):
+    _cls = bpy.types.NearSensor
+    _constants = dict(
+        type = 'near_sensor'
+    )
+    
+    distance = Auto()
+    property = Auto()
+    reset_distance = Auto()
+
+class Actuator(AutoClass):
+    #_cls = bpy.types.Actuator
+    
+    name = Auto()
+
+class ShapeActionActuator(Actuator):
+    _cls = bpy.types.ShapeActionActuator
+    _constants = dict(
+        type = 'shape_action_actuator'
+    )
+    
+    #action =
+    frame_blend_in = Auto()
+    frame_end = Auto()
+    frame_property = Auto()
+    frame_start = Auto()
+    mode = Auto()
+    priority = Auto()
+    property = Auto()
+    use_continue_last_frame = Auto() 
+
+class VisibilityActuator(Actuator):
+    _cls = bpy.types.VisibilityActuator
+    _constants = dict(
+        type = 'visibility_actuator'
+    )
+    
+    apply_to_children = Auto()
+    use_occlusion = Auto()
+    use_visible = Auto()
+
+class MessageActuator(Actuator):
+    _cls = bpy.types.MessageActuator
+    _constants = dict(
+        type = 'message_actuator'
+    )
+    
+    body_message = Auto()
+    body_property = Auto()
+    body_type = Auto()
+    subject = Auto()
+    to_property = Auto()
+    
+class Filter2DActuator(Actuator):
+    _cls = bpy.types.Filter2DActuator
+    _constants = dict(
+        type = 'filter_2d_actuator'
+    )
+    
+    filter_pass = Auto()
+    #glsl_shader = Auto()
+    mode = Auto()
+    motion_blur_factor = Auto()
+    use_motion_blur = Auto()
+
+class GameActuator(Actuator):
+    _cls = bpy.types.GameActuator
+    _constants = dict(
+        type = 'game_actuator'
+    )
+    
+    filename = Auto()
+    mode = Auto()
+
+class RandomActuator(Actuator):
+    _cls = bpy.types.RandomActuator
+    _constants = dict(
+        type = 'random_actuator'
+    )
+    
+    chance = Auto()
+    distribution = Auto()
+    float_max = Auto()
+    float_mean = Auto()
+    float_min = Auto()
+    float_value = Auto()
+    half_life_time = Auto()
+    int_max = Auto()
+    int_mean = Auto()
+    int_min = Auto()
+    int_value = Auto()
+    property = Auto()
+    seed = Auto()
+    standard_derivation = Auto()
+    use_always_true = Auto()
+
+class SteeringActuator(Actuator):
+    _cls = bpy.types.SteeringActuator
+    _constants = dict(
+        type = 'steering_actuator'
+    )
+    
+    acceleration = Auto()
+    distance = Auto()
+    facing = Auto()
+    facing_axis = Auto()
+    mode = Auto()
+    #navmesh
+    normal_up = Auto()
+    self_terminated = Auto()
+    show_visualization = Auto()
+    target = Auto()
+    turn_speed = Auto()
+    update_period = Auto()
+    velocity = Auto()
+
+class ArmatureActuator(Actuator):
+    _cls = bpy.types.ArmatureActuator
+    _constants = dict(
+        type = 'armature_actuator'
+    )
+    
+    bone = Auto()
+    constraint = Auto()
+    influence = Auto()
+    mode = Auto()
+    #secondary_target = Auto()
+    #target = Auto()
+    weight = Auto()
+
+class SoundActuator(Actuator):
+    _cls = bpy.types.SoundActuator
+    _constants = dict(
+        type = 'sound_actuator'
+    )
+    
+    cone_inner_angle_3d = Auto()
+    cone_outer_angle_3d = Auto()
+    cone_outer_gain_3d = Auto()
+    distance_3d_max = Auto()
+    distance_3d_reference = Auto()
+    gain_3d_max = Auto()
+    gain_3d_min = Auto()
+    mode = Auto()
+    pitch = Auto()
+    rolloff_factor_3d = Auto()
+    #sound =
+    use_sound_3d = Auto()
+    volume = Auto()
+
+class ParentActuator(Actuator):
+    _cls = bpy.types.ParentActuator
+    _constants = dict(
+        type = 'parent_actuator'
+    )
+    
+    mode = Auto()
+    #object =
+    use_compound = Auto()
+    use_ghost = Auto() 
+
+class SceneActuator(Actuator):
+    _cls = bpy.types.SceneActuator
+    _constants = dict(
+        type = 'scene_actuator'
+    )
+    
+    #camera = Auto()
+    mode = Auto()
+    #scene = Auto()
+
+class StateActuator(Actuator):
+    _cls = bpy.types.StateActuator
+    _constants = dict(
+        type = 'state_actuator'
+    )
+    
+    operation = Auto()
+    states = Auto()
+
+class ActionActuator(Actuator):
+    _cls = bpy.types.ActionActuator
+    _constants = dict(
+        type = 'action_actuator'
+    )
+    
+    #action =
+    apply_to_children = Auto()
+    frame_blend_in = Auto()
+    frame_end = Auto()
+    frame_property = Auto()
+    frame_start = Auto()
+    layer = Auto()
+    layer_weight = Auto()
+    play_mode = Auto()
+    priority = Auto()
+    property = Auto()
+    use_additive = Auto()
+    use_continue_last_frame = Auto()
+    use_force = Auto()
+    use_local = Auto()
+
+class CameraActuator(Actuator):
+    _cls = bpy.types.CameraActuator
+    _constants = dict(
+        type = 'camera_actuator'
+    )
+    
+    axis = Auto()
+    damping = Auto()
+    height = Auto()
+    max = Auto()
+    min = Auto()
+    object = AutoRef()
+
+class ConstraintActuator(Actuator):
+    _cls = bpy.types.ConstraintActuator
+    _constants = dict(
+        type = 'constraint_actuator'
+    )
+    
+    angle_max = Auto()
+    angle_min = Auto()
+    damping = Auto()
+    damping_rotation = Auto()
+    direction = Auto()
+    direction_axis = Auto()
+    direction_axis_pos = Auto()
+    distance = Auto()
+    fh_damping = Auto()
+    fh_force = Auto()
+    fh_height = Auto()
+    limit = Auto()
+    limit_max = Auto()
+    limit_min = Auto()
+    material = Auto()
+    mode = Auto()
+    property = Auto()
+    range = Auto()
+    rotation_max = Auto()
+    time = Auto()
+    use_fh_normal = Auto()
+    use_fh_paralel_axis = Auto()
+    use_force_distance = Auto()
+    use_local = Auto()
+    use_material_defect = Auto()
+    use_normal = Auto()
+    use_persistent = Auto()
+
+class PropertyActuator(Actuator):
+    _cls = bpy.types.PropertyActuator
+    _constants = dict(
+        type = 'property_actuator'
+    )
+    
+    mode = Auto()
+    object = AutoRef()
+    object_property = Auto()
+    property = Auto()
+    value = Auto()
+
+class EditObjectActuator(Actuator):
+    _cls = bpy.types.EditObjectActuator
+    _constants = dict(
+        type = 'edit_object_actuator'
+    )
+    
+    angular_velocity = Auto()
+    dynamic_operation = Auto()
+    linear_velocity = Auto()
+    mass = Auto()
+    mesh = AutoRef()
+    mode = Auto()
+    object = AutoRef()
+    time = Auto()
+    track_object = AutoRef()
+    use_3d_tracking = Auto()
+    use_local_angular_velocity = Auto()
+    use_local_linear_velocity = Auto()
+    use_replace_display_mesh = Auto()
+    use_replace_physics_mesh = Auto()
+
+class ObjectActuator(Actuator):
+    _cls = bpy.types.ObjectActuator
+    _constants = dict(
+        type = 'motion_actuator'
+    )
+    
+    angular_velocity = Auto()
+    damping = Auto()
+    derivate_coefficient = Auto()
+    force = Auto()
+    force_min_x = Auto()
+    force_min_y = Auto()
+    force_min_z = Auto()
+    force_max_x = Auto()
+    force_max_y = Auto()
+    force_max_z = Auto()
+    integral_coefficient = Auto()
+    linear_velocity = Auto()
+    mode = Auto()
+    offset_location = Auto()
+    offset_rotation = Auto()
+    proportional_coefficient = Auto()
+    # reference_object = 
+    torque = Auto()
+    use_add_linear_velocity = Auto()
+    use_local_angular_velocity = Auto()
+    use_local_force = Auto()
+    use_local_linear_velocity = Auto()
+    use_local_location = Auto()
+    use_local_rotation = Auto()
+    use_local_torque = Auto()
+    use_servo_limit_x = Auto()
+    use_servo_limit_y = Auto()
+    use_servo_limit_z = Auto()
+
+class Controller(AutoClass):
+    #_cls = bpy.types.Controller
+    
+    name = Auto()
+    states = Auto()
+    use_priority = Auto()
+    actuators = Auto(pack=items_to_names)
+
+class ExpressionController(Controller):
+    _cls = bpy.types.ExpressionController
+    _constants = dict(
+        type = 'expression_controller'
+    )
+    
+    expression = Auto()
+
+class XnorController(Controller):
+    _cls = bpy.types.XnorController
+    _constants = dict(
+        type = 'logic_xnor_controller'
+    )
+
+class AndController(Controller):
+    _cls = bpy.types.AndController
+    _constants = dict(
+        type = 'logic_and_controller'
+    )
+
+class NorController(Controller):
+    _cls = bpy.types.NorController
+    _constants = dict(
+        type = 'logic_nor_controller'
+    )
+
+class OrController(Controller):
+    _cls = bpy.types.OrController
+    _constants = dict(
+        type = 'logic_or_controller'
+    )
+
+class XorController(Controller):
+    _cls = bpy.types.XorController
+    _constants = dict(
+        type = 'logic_xor_controller'
+    )
+
+class NandController(Controller):
+    _cls = bpy.types.NandController
+    _constants = dict(
+        type = 'logic_nand_controller'
+    )
+
+class PythonController(Controller):
+    _cls = bpy.types.PythonController
+    _constants = dict(
+        type = 'python_controller'
+    )
+    
+    mode = Auto()
+    module = Auto()
+    use_debug = Auto()
+    
+class Curve(FromLib):
+    _cls = bpy.types.Curve
+    _group = 'curves'
+    _constants = dict(
+        type = 'curve'
+    )
+    
+class SurfaceCurve(Curve):
+    _cls = bpy.types.SurfaceCurve
+    _constants = dict(
+        type = 'surface_curve'
+    )
+
+class TextBox(AutoClass):
+    _cls = bpy.types.TextBox
+    _resolve = lambda o: [o.x,o.y,o.width,o.height]
+    
+class VectorFont(FromLib):
+    _cls = bpy.types.VectorFont
+    _group = 'fonts'
+    _constants = dict(
+        type = 'font'
+    )
+    
+    filepath = Auto()
+    
+class TextCharacterFormat(AutoClass):
+    _cls = bpy.types.TextCharacterFormat
+    _constants = dict(
+        type = 'text_character_format'
+    )
+    use_bold = Auto()
+    use_italic = Auto()
+    use_small_caps = Auto()
+    use_underline = Auto()
+
+class TextCurve(Curve):
+    _cls = bpy.types.TextCurve
+    _constants = dict(
+        type = 'text_curve'
+    )
+    
+    active_textbox = Auto()
+    align = Auto()
+    body = Auto()
+    body_format = Auto()
+    edit_format = Auto()
+    family = Auto()
+    offset_x = Auto()
+    offset_y = Auto()
+    shear = Auto()
+    size = Auto()
+    small_caps_scale = Auto()
+    space_character = Auto()
+    space_line = Auto()
+    space_word = Auto()
+    underline_height = Auto()
+    underline_position = Auto()
+    text_boxes = Auto()
+    font = Auto()
+    font_bold = Auto()
+    font_bold_italic = Auto()
+    font_italic = Auto()
+    
+################################################################################
+    
+AutoClass._scan(locals())
+
+################################################################################
+
+class PropTracker(object):
+    def __init__(self, getfunc):
+        self.getfunc = getfunc
+        self.data = None
+    
+    def changed_update(self):
+        if self.changed():
+            self.update()
+            return True
+        return False
+    
+    def changed(self):
+        old_data = self.data
+        data = self.getfunc()
+        if old_data is data:
+            return False
+        return not (old_data == data)
+        
+    def update(self):
+        self.data = self.getfunc()
+
+################################################################################
+
+class ArchiveClient(DataArchive):
+    def __init__(self):
+        DataArchive.__init__(self)
+        
+    def poll(self):
+        pass
+    
+    def close(self):
+        pass
+
+################################################################################
+
+class BlenderClient(object):
+    instance = None
+    MAX_TODO_TIME = 1.0/60.0
+
+    @classmethod
+    def start(cls):
+        if cls.instance:
+            return
+        client = BlenderClient()
+    
+    @classmethod
+    def startstop(cls):
+        if not cls.instance:
+            cls.start()
+        else:
+            cls.instance.disconnect()
+    
+    @classmethod
+    def update_post(cls):
+        client = cls.instance
+        if not client:
+            return
+        client.poll()
+        
+    @classmethod
+    def export(cls, path, stamp = None):
+        if cls.instance:
+            cls.instance.disconnect()
+        t = time.time()
+        log.info("Serializing data for archive {}".format(path))
+        cls.start()
+        log.info("Data serialized in {:.3g}s.".format(time.time() - t))
+        assert not cls.instance._todo_list
+        cls.instance.endp.setitem('__stamp__/value', stamp)
+        cls.instance.endp.write_archive(path)
+        cls.instance.disconnect()
+    
+    def __init__(self):
+        assert not BlenderClient.instance
+        BlenderClient.instance = self
+        self.ctx_scene = PropTracker(lambda: bpy.context.scene)
+        self.ctx_selected = PropTracker(lambda: list(bpy.context.selected_objects))
+        self.endp = ArchiveClient()
+        self._todo_list = {}
+        self.root_changed()
+        
+    def disconnect(self):
+        BlenderClient.instance = None
+        self.endp.close()
+    
+    def get_libroot(self, library):
+        return self.ns_root + '/libraries/' + get_libid(library)
+
+    def get_objroot(self, obj):
+        return self.get_libroot(obj.library)
+        
+    def resolve_image_path(self, img):
+        return bpy.path.relpath(os.path.normpath(
+            bpy.path.abspath(img.filepath,
+            library = img.library)))
+
+    def root_changed(self):
+        filepath = os.path.normpath(os.path.abspath(bpy.data.filepath))
+        
+        self.ns_root = self.get_root_ns()
+        self.endp.setitem(self.ns_root + '/filepath', filepath)
+        
+        def add_ns(library):
+            libroot = self.get_libroot(library)
+            libpath = get_libpath(library)
+            
+            self.endp.setitem(libroot + '/libpath', libpath)
+            self.endp.setitem(libroot + '/scenes', {})
+            self.endp.setitem(libroot + '/objects', {})
+            self.endp.setitem(libroot + '/cameras', {})
+            self.endp.setitem(libroot + '/meshes', {})
+            self.endp.setitem(libroot + '/materials', {})
+            self.endp.setitem(libroot + '/groups', {})
+        
+        add_ns(None)
+        for lib in bpy.data.libraries:
+            add_ns(lib)
+
+        for scene in bpy.data.scenes:
+            log.debug("dumping scene {0}".format(scene))
+            reference(scene)
+
+        for group in bpy.data.groups:
+            log.debug("dumping group {0}".format(group))
+            reference(group)
+            
+        self.handle_todo_list()
+        
+    def get_todo_list_count(self):
+        return len(self._todo_list)
+        
+    def commit(self, cls, uri, value):
+        log.debug('updating',uri)
+        cls._on_purify(value)
+        self.endp.setitem(uri, purify(value))
+        
+    def handle_todo_list(self):
+        count = 0
+        timestamp = time.time()
+        while self._todo_list:
+            uri, (cls, value) = self._todo_list.popitem()
+            self.commit(cls, uri, value)
+            count += 1
+            if 0:
+                if (time.time() - timestamp) > self.MAX_TODO_TIME:
+                    # need to do more next frame
+                    break
+    
+    def get_root_ns(self):
+        return ""
+
+    def check_changes(self):
+        update_ctx = False
+        
+        if self.ctx_scene.changed_update():
+            scene_uri = reference(bpy.context.scene)
+            self.endp.setitem(self.ns_root + '/context/scene', scene_uri)
+            
+        if self.ctx_selected.changed_update():
+            objects = reference_list(bpy.context.selected_objects)
+            self.endp.setitem(self.ns_root + '/context/selected_objects', objects)
+            
+        for obj in bpy.context.selected_objects:
+            uri = reference(obj)
+            self.endp.setitem(
+                uri + '/matrix_world',
+                matrix_to_list(obj.matrix_world))
+    
+    def poll(self):
+        self.handle_todo_list()
+        self.endp.poll()
+        self.check_changes()    
+
+################################################################################

          
A => liminal_legacy/liminal/bootstrap.py +272 -0
@@ 0,0 1,272 @@ 
+from __future__ import (print_function, division, absolute_import)
+
+from glm import (
+    vec2,vec3,vec4,
+    ivec2,ivec3,ivec4,
+    mat2,mat3,mat4,
+    X_AXIS, Y_AXIS, Z_AXIS,
+    AABB)
+
+from .engine.actor import (
+    Actor)
+
+from .engine.audio import (
+    Sound,
+    Source,
+    Channel,
+    AudioManager)
+
+from .engine.archive import (
+    ArchiveManager, 
+    resolve_path)
+
+from .engine.camera import (
+    ScreenCamera,
+    DebugCamera)
+
+from .engine.light import (
+    PointLight,
+    SpotLight,
+    DirectionalLight)
+
+from .engine.config import (
+    Config,
+    StereoMode)
+
+from .engine.core import (
+    FXAAMode,
+    RenderManager)
+
+from .engine.font import (
+    Font)
+
+from .engine.draw import (
+    LinePainter, 
+    FontPainter,
+    PolygonPainter)
+
+from .engine.input import (
+    GamepadAxis, 
+    GamepadButton,
+    GamepadHat,
+    InputState,
+    InputSet,
+    Mouse,
+    Binding,
+    BindingList)
+
+from .engine.interface import (
+    Named,
+    NamedSet,
+    RNode)
+
+from .engine.material import (
+    Material) 
+
+from .engine.mesh import (
+    MeshFormats,
+    MeshBuffer, 
+    VertexArray)
+
+from .engine.ppfx import (
+    PPFX)
+
+from .engine.deferred import (
+    GRenderManager, 
+    GMaterial,
+    GMaterialSDF)
+
+from .engine.render import (
+    Viewport,
+    RProxy)
+
+from .engine.shader import (
+    Program)
+
+from .engine.system import (
+    System)
+
+from .engine.framebuffer import (
+    Framebuffer,
+    FramebufferTexture2D
+)
+
+from .engine.texture import (
+    Image,
+    BufferTexture1D,
+    ImageTexture1D,
+    ImageTexture2D,
+    ImageTexture2DArray,
+    ImageTexture3D,
+    ImageTextureCubeMap,
+    TextureWrap,
+    blank_texture,
+    zero_texture,
+    lut_default_texture,
+    CubeMapFormat)
+
+from .engine.timing import (
+    TimeSource,
+    Scheduler,
+    SchedulerThread, 
+    TimeManager)
+
+from .engine.physics import (
+    PhysicsManager,
+    Space,
+    Body,
+    WorldSpace,
+    Box,
+    Sphere,
+    Plane,
+    Capsule,
+    Cylinder,
+    Ray,
+    Mass,
+    Density,
+    TriMeshData,
+    TriMesh,
+    ContactEvaluator,
+    BallJoint,
+    HingeJoint,
+    SliderJoint,
+    ContactJoint,
+    UniversalJoint,
+    Hinge2Joint,
+    PRJoint,
+    PUJoint,
+    PistonJoint,
+    FixedJoint,
+    AMotorJoint,
+    LMotorJoint,
+    Plane2DJoint,
+    Picker
+    )
+
+from .runtime import Runtime
+
+from .utils import (
+        linear_vec3,
+        linear_vec4,
+        mix,
+        clamp,
+        smoothstep,
+        srgb_decode,
+        srgb_encode        
+    )
+
+from .spectre import Spectre
+
+from . import sigil
+from .sigil import DrawContext, DrawSurface
+
+__all__ = [
+    'vec2','vec3','vec4',
+    'ivec2','ivec3','ivec4',
+    'mat2','mat3','mat4',
+    'X_AXIS', 'Y_AXIS', 'Z_AXIS',
+    'Sound',
+    'Source',
+    'Channel',
+    'AudioManager',
+    'Actor',
+    'ArchiveManager', 
+    'resolve_path',
+    'ScreenCamera',
+    'DebugCamera',
+    'PointLight',
+    'SpotLight',
+    'DirectionalLight',
+    'Config',
+    'StereoMode',
+    'FXAAMode',
+    'RenderManager',
+    'Font',
+    'LinePainter', 
+    'FontPainter',
+    'PolygonPainter',
+    'GamepadAxis', 
+    'GamepadButton',
+    'GamepadHat',
+    'InputState',
+    'InputSet',
+    'Mouse',
+    'Binding',
+    'BindingList',
+    'Named',
+    'NamedSet',
+    'RNode',
+    'RProxy',
+    'Material',
+    'AABB', 
+    'VertexArray',
+    'MeshBuffer',
+    'MeshFormats',
+    'PPFX',
+    'GRenderManager',
+    'GMaterial',
+    'GMaterialSDF',
+    'Viewport',
+    'Program',
+    'System',
+    'Image',
+    'Framebuffer',
+    'FramebufferTexture2D',
+    'BufferTexture1D',
+    'ImageTexture1D',
+    'ImageTexture2D',
+    'ImageTexture2DArray',
+    'ImageTexture3D',
+    'ImageTextureCubeMap',
+    'TextureWrap',
+    'blank_texture',
+    'zero_texture',
+    'lut_default_texture',
+    'CubeMapFormat',
+    'TimeSource', 
+    'Scheduler',  
+    'SchedulerThread',  
+    'TimeManager',
+    'Runtime',
+    'linear_vec3',
+    'linear_vec4',
+    'mix',
+    'clamp',
+    'smoothstep',
+    'PhysicsManager',
+    'WorldSpace',
+    'Space',
+    'Body',
+    'Box',
+    'Sphere',
+    'Plane',
+    'Capsule',
+    'Cylinder',
+    'Ray',
+    'Mass',
+    'Density',    
+    'TriMeshData',
+    'TriMesh',
+    'ContactEvaluator',
+    'BallJoint',
+    'HingeJoint',
+    'SliderJoint',
+    'ContactJoint',
+    'UniversalJoint',
+    'Hinge2Joint',
+    'PRJoint',
+    'PUJoint',
+    'PistonJoint',
+    'FixedJoint',
+    'AMotorJoint',
+    'LMotorJoint',
+    'Plane2DJoint',
+    'Picker',
+    'srgb_decode',
+    'srgb_encode',
+    'Spectre',
+    'sigil',
+    'DrawContext',
+    'DrawSurface'    
+]
+

          
A => liminal_legacy/liminal/data/__init__.py +0 -0

        
A => liminal_legacy/liminal/data/builder.py +508 -0
@@ 0,0 1,508 @@ 
+"""
+The builder assembles engine objects from serialized server or archive data
+(effectively anything that implements the .source.DataSource interface.).
+
+Conversions are asynchroneous and may take up to a few seconds to complete.    
+"""
+
+from __future__ import (print_function, division, absolute_import)
+
+import os
+import sys
+import logging
+import weakref
+import inspect
+import importlib
+from pprint import pprint
+
+from glm import (vec3, mat4, flatten) #@UnresolvedImport
+
+from ..extras.purify import AutoClass, Auto, Annotate, AutoAttr, assemble
+from ..engine.interface import NamedSet
+from ..engine.shader import CustomShader, BlenderShader
+from ..engine.mesh import GLMesh
+from ..engine.material import GLMaterial
+from ..engine.actor import Actor #@UnresolvedImport
+from ..engine.camera import Camera 
+from ..engine.scene import Scene, SceneManager
+from ..engine.archive import ArchiveManager
+from .. import engine
+
+################################################################################
+
+log = logging.getLogger('liminal.builder')
+
+################################################################################
+
+def to_mat4(data):
+    return mat4(*flatten(data)).transpose()
+
+def uri_split(uri):
+    idx = uri.rindex('/')
+    return uri[:idx], uri[idx+1:]
+
+def uri_base(uri):
+    return uri[uri.rindex('/')+1:]
+
+def inject_material_shader(data):
+    shader = BlenderShader()
+    shader._name = data['name']
+    shader._shaderdef = data['shader']
+    return shader
+    
+################################################################################
+
+class _BindMesh(AutoClass):
+    _cls = GLMesh
+    _group = 'meshes'
+    _constants = dict(
+        type = 'mesh'
+    )
+    
+    name = Auto()
+    material_indices = Auto()
+    vertices = Auto()
+    normals = Auto()
+    faces = Auto()
+    vertex_colors = Auto()
+    texcoords = Auto()
+
+class _BindMaterial(AutoClass):
+    _cls = GLMaterial
+    _group = 'materials'
+    _constants = dict(
+        type = 'material'
+    )
+
+    name = Auto()
+    invisible = Auto()
+    use_backface_culling = Auto()
+    use_vertex_color_paint = Auto()
+    diffuse_color = Auto()
+    shader = Annotate(None, inject_material_shader)
+    alpha_blend = Auto()
+    use_transparency = Auto()
+    alpha = Auto()
+    cutout = Auto()
+        
+class _BindScene(AutoClass):
+    _cls = Scene
+    _group = 'scenes'
+    _constants = dict(
+        type = 'scene',
+    )
+
+    scenepass = Auto()
+    name = Auto()
+    layers = Auto()
+
+    #fps = Auto()
+    #logic_step_max = Auto()
+    #obstacle_simulation = Auto()
+    #occlusion_culling_resolution = Auto()
+    physics_engine = Auto()
+    physics_gravity = Auto()
+    #physics_step_max = Auto()
+    #physics_step_sub = Auto()
+    #show_debug_properties = Auto()
+    #show_framerate_profile = Auto()
+    #use_activity_culling = Auto()
+    #use_occlusion_culling = Auto()
+
+def import_module_class(modulename, classname):
+    mod = importlib.import_module(modulename)
+    log.debug("retrieving class {0} from {1}".format(
+        classname, mod))
+    try:
+        class_ = getattr(mod, classname)
+    except AttributeError:
+        log.error("Module '{0}' has no attribute '{1}'".format(modulename, classname))
+        return
+
+    return class_
+    
+class _BindObject(AutoClass):
+    _cls = Actor
+    
+    @classmethod
+    def validate_module(cls, value):
+        return value
+    
+    @classmethod
+    def _cls_factory(cls, props):
+        classpath = props.get('python_class',None)
+        if classpath:
+            classpath = cls.validate_module(classpath)
+            idx = classpath.rindex('.')
+            modulename,classname = classpath[:idx],classpath[idx+1:]
+            modulepath = modulename.replace('.','/')
+            modulepath = ArchiveManager().resolve_path('//' + modulepath + '.py') #@UndefinedVariable
+            log.debug("Initializing module {0}".format(modulepath))
+            assert os.path.isfile(modulepath), "file {0} does not exist".format(modulepath)
+            #basedir = os.path.dirname(modulepath)
+            #if not basedir in sys.path:
+            #    sys.path.insert(0, basedir)
+            return import_module_class(modulename, classname)()
+        else:
+            return cls._cls()
+    
+    _group = 'objects'
+    _constants = dict(
+        type = 'object',
+    )
+
+    name = Auto()
+    matrix_local = AutoAttr.o.transform(unpack=to_mat4)
+    game_properties = Auto()
+    hide_render = AutoAttr.o.visible(unpack=lambda v:not v)
+    
+    physics_type = Auto()
+    use_collision_bounds = Auto()
+    collision_bounds_type = Auto()
+    mass = Auto()
+    radius = Auto()
+    damping = Auto()
+    rotation_damping = Auto()
+    use_actor = Auto()
+    use_ghost = Auto()
+        
+    #python_class = Auto()
+        
+    #collision_margin = AutoAttr.o.game.collision_margin()
+    #use_collision_compound = AutoAttr.o.game.use_collision_compound()
+    #use_actor = AutoAttr.o.game.use_actor()
+    #use_ghost = AutoAttr.o.game.use_ghost()
+    
+
+class _BindLamp(_BindObject):
+    _group = 'lamps'
+    _constants = dict(
+        type = 'lamp',
+    )
+    
+class _BindCamera(_BindObject):
+    _cls = Camera
+    _group = 'cameras'
+    _constants = dict(
+        type = 'camera'
+    )
+        
+    angle_y = AutoAttr.o.lens()
+    clip_start = AutoAttr.o.near()
+    clip_end = AutoAttr.o.far()
+    sensor_width = Auto()
+    sensor_height = Auto()
+    ortho_scale = Auto()
+    #sensor_fit = Auto()
+    #shift_x = Auto()
+    #shift_y = Auto()
+    mode = AutoAttr.o.perspective(unpack=lambda k: k == 'PERSP')    
+
+################################################################################
+
+AutoClass._scan(locals())
+
+################################################################################
+
+class Library(object):
+    def __init__(self, name, uri, path):
+        self.name = name
+        self.uri = uri
+        self.path = path
+        self.meshes = NamedSet()
+        self.materials = NamedSet()
+        self.actors = NamedSet()
+        self.scenes = NamedSet()
+
+################################################################################
+
+class SceneBuilder(object):
+    instance = None
+    
+    def __init__(self, datasource):
+        self.src = datasource
+        SceneBuilder.instance = weakref.ref(self)
+        self.model = None
+        self.ns_libraries = '/libraries'
+        self.libs = dict()
+        self.groups = {}
+        self.meshes = {}
+        self.materials = {}
+        self.textures = {}
+    
+    def lib(self, data=None):
+        libid = data and data.get('library',None)
+        return self.libs[libid]
+        
+    def get_sessions(self):
+        keys = self.src.getkeys('/session')
+        result = {}
+        for entry in keys:
+            result[entry] = self.src.getitem('/session/' + entry + '/filepath')
+        return result
+    
+    def init(self):
+        self.libs[None] = Library('<tmp>', None, None)
+        
+        libnames = self.src.getkeys(self.ns_libraries)
+        for libname in libnames:
+            libkey = self.ns_libraries + '/' + libname
+            libpath = self.src.getitem(libkey + '/libpath')
+            libbasename = os.path.splitext(os.path.basename(libpath))[0]
+            self.libs[libname] = Library(libbasename, libkey, libpath)
+            
+            ns_groups = libkey + '/groups'
+            for key in self.src.getkeys(ns_groups):
+                if key in self.groups:
+                    log.warn('Duplicate group name: {}'.format(key))
+                    continue
+                self.groups[key] = ns_groups + '/' + key
+            
+            ns_meshes = libkey + '/meshes'
+            for key in self.src.getkeys(ns_meshes):
+                if key in self.meshes:
+                    log.warn('Duplicate mesh name: {}'.format(key))
+                    continue
+                self.meshes[key] = ns_meshes + '/' + key
+
+            ns_materials = libkey + '/materials'
+            for key in self.src.getkeys(ns_materials):
+                if key in self.materials:
+                    log.warn('Duplicate material name: {}'.format(key))
+                    continue
+                self.materials[key] = ns_materials + '/' + key
+
+            ns_textures = libkey + '/textures'
+            for key in self.src.getkeys(ns_textures):
+                if key in self.textures:
+                    log.warn('Duplicate texture name: {}'.format(key))
+                    continue
+                self.textures[key] = ns_textures + '/' + key
+                        
+    def add_scene(self, name):
+        for lib in self.libs.values():
+            if not lib.uri:
+                continue
+            ns_scenes = lib.uri + '/scenes'
+            scenekey = ns_scenes + '/' + name
+            if self.src.hasitem(scenekey):
+                scene = self.build_scene(self.src.getitem(scenekey))
+                SceneManager().add_scene(scene)
+                return scene
+            
+    def build_scene_content(self, sc, scene):
+        if sc.get('background_set',None):
+            bgscene_uri = sc['background_set']
+            self.build_scene_content(self.src.getitem(bgscene_uri), scene)
+            
+        child_uri_map = {}
+        dupli_groups = []
+            
+        for ob_base in sc['object_bases']:
+            active = (ob_base['layers'] & scene.layers)
+            uri = ob_base['object']
+            obj, child_uris, dupli_group = self.build_object(uri)
+            if dupli_group:  
+                dupli_groups.append((obj, dupli_group, active))
+            child_uri_map[uri] = obj, child_uris
+            if active:
+                scene.add_actor(obj)
+            else:
+                scene.add_inactive_actor(obj)
+        
+        self.connect_children(child_uri_map)
+        
+        self.build_dupli_groups(scene, dupli_groups)
+            
+    def connect_children(self, child_uri_map):
+        top_uris = set(child_uri_map.keys())
+        # connect children to parents
+        for uri, (obj, child_uris) in child_uri_map.items():
+            for child_uri in child_uris:
+                top_uris.discard(child_uri)
+                child_obj = child_uri_map[child_uri][0]
+                obj.add_child(child_obj)
+        return top_uris
+
+    def build_dupli_group(self, parent, groupname, active = True):
+        """
+        parent: parent Actor to attach dupli group to
+        offset: vec3
+        uris: list of actors to replicate here
+        active: whether to add actors to active or inactive actors
+        """
+        
+        group_uri = self.groups.get(groupname, None)
+        assert group_uri, "group {} not found in any library".format(groupname)
+        
+        return self.build_dupli_groups(parent.scene, [
+            (parent, group_uri, active)
+        ])
+            
+    def build_dupli_groups(self, scene, dupli_groups):
+        child_uri_map = {}
+        
+        # add dupli groups
+        while dupli_groups:
+            parent_obj, group_uri, active = dupli_groups.pop(0)
+            group = self.src.getitem(group_uri)
+        
+            ox,oy,oz = group['dupli_offset']
+            
+            inactive_objects = set()
+            
+            for uri in group['objects']:
+                obj, child_uris, dupli_group = self.build_object(uri, dupli = True)
+                if dupli_group:
+                    dupli_groups.append((obj, dupli_group, active))
+                child_uri_map[uri] = obj, child_uris
+                obj.game_properties = tuple(obj.game_properties) + tuple(
+                    parent_obj.game_properties)
+                if active:
+                    scene.add_actor(obj)
+                else:
+                    scene.add_inactive_actor(obj)
+                    inactive_objects.add(uri)
+            
+            top_uris = self.connect_children(child_uri_map)
+            for uri in top_uris:
+                obj = child_uri_map[uri][0] 
+                mtx = obj.world_transform
+                mtx = mtx.translate_fff(-ox,-oy,-oz)
+                mtx = parent_obj.world_transform.mul_mat4(mtx)
+                obj._dupli_transform = parent_obj.world_transform.copy() 
+                obj.world_transform = mtx
+                parent_obj._dupli_list.append(obj)
+            
+            child_uri_map = {}
+                    
+    def build_scene(self, sc):
+        lib = self.lib(sc)      
+        scene = lib.scenes.get(sc['name'], None)
+        if scene:
+            return scene
+        scene = assemble(sc)
+        lib.scenes.add(scene)
+            
+        log.debug("Building scene {0}".format(scene._name))
+        self.build_scene_content(sc, scene)
+        
+        cam_uri = sc['camera']
+        if cam_uri:
+            scene.active_camera = scene.cameras[uri_base(cam_uri)]
+        return scene
+    
+    def get_object(self, uri):
+        ob = self.src.getitem(uri)
+        lib = self.lib(ob)
+        return lib.actors.get(ob['name'], None)
+    
+    def build_object(self, uri, dupli = False):
+        ob = self.src.getitem(uri)
+        lib = self.lib(ob)
+        data_uri = ob['data']
+        if not (data_uri is None):
+            data = self.src.getitem(data_uri)
+            if data['type'] in ('lamp','camera'):
+                data.pop('name', None) # don't overwrite name attribute
+                ob.update(data)
+        else:
+            data = None
+        obj = assemble(ob)
+        if not dupli:
+            lib.actors.add(obj)
+        log.debug("Building object {0}".format(obj.name))
+        
+        if data and data['type'] == 'mesh':
+            mesh = self.build_mesh(data, obj)
+            obj.add_mesh(mesh)
+            
+        return obj, ob.get('children',[]), ob['dupli_group']
+    
+    def build_lamp(self, data):
+        return Actor()
+    
+    def build_camera(self, data):
+        return assemble(data)
+    
+    def build_texture(self, uri):
+        if uri is None:
+            return
+        if not uri.startswith('/'):
+            tex_uri = self.textures.get(uri, None)
+            assert tex_uri, "texture {} not found in any library.".format(
+                uri)
+            uri = tex_uri
+                    
+        tex = self.src.getitem(uri)
+        img_uri = tex.get('image',None)
+        if not img_uri:
+            return None
+        img = self.src.getitem(img_uri)
+        image = engine.texture.Image()
+        image.filepath = img['filepath']
+        texture = engine.texture.GLImageTexture2D()
+        texture.image = image
+        return texture
+    
+    def build_material(self, mat, obj = None):
+        if isinstance(mat, basestring):
+            mat_uri = self.materials.get(mat, None)
+            assert mat_uri, "material {} not found in any library.".format(
+                mat)
+            
+            mat = self.src.getitem(mat_uri)        
+        
+        material = None
+        lib = self.lib(mat)
+        if mat:
+            log.debug("Building material {0}".format(mat['name']))
+            material = lib.materials.get(mat['name'], None)
+        if not material:
+            if mat:
+                material = assemble(mat)
+                
+                lib.materials.add(material)
+                for i, texture_slot in enumerate(mat['textures']):
+                    if texture_slot:
+                        texture_uri = texture_slot['texture']
+                        texture = self.build_texture(texture_uri)
+                        material.set_texture(i, texture)
+            else:
+                material = GLMaterial()
+                matshader = BlenderShader() 
+                material.shader = matshader
+            
+        if obj and obj.has_shader_props():
+            log.debug("Assigning custom shader to {0}".format(material))
+            shader = CustomShader()
+            shader.set_var_source(obj)
+            material.shader = shader
+        
+        return material
+
+    def build_mesh(self, data, obj = None):
+        if isinstance(data, basestring):
+            mesh_uri = self.meshes.get(data, None)
+            assert mesh_uri, "mesh {} not found in any library".format(data)
+            data = self.src.getitem(mesh_uri)
+        
+        lib = self.lib(data)
+        mesh = lib.meshes.get(data['name'], None)
+        if mesh:
+            return mesh
+        mesh = assemble(data)
+        lib.meshes.add(mesh)
+        
+        for i,mat_uri in enumerate(data['materials']):
+            if mat_uri:
+                mat = self.src.getitem(mat_uri)
+            else:
+                log.warn("mesh {0} has no material in slot {1}".format(mesh, i))
+                mat = None
+            material = self.build_material(mat, obj)
+            mesh.add_material(material)
+        
+        return mesh
+
+################################################################################

          
A => liminal_legacy/liminal/data/export_blendc.py +37 -0
@@ 0,0 1,37 @@ 
+
+# should be run from within Blender
+
+import os
+import sys
+import bpy
+import argparse
+import addon_utils  
+
+def run():
+    is_enabled, is_loaded = addon_utils.check('liminalclient')
+    if not is_loaded:
+        print("You need to install the liminal blenderclient first.")
+        raise SystemExit(255)
+    if not is_enabled:
+        bpy.ops.wm.addon_enable(module="liminalclient")
+    
+    parser = argparse.ArgumentParser()
+    parser.add_argument('--liminalblob', dest='filepath', required = True,
+            help='The file to export the liminalblob to')
+    parser.add_argument('--stamp', dest='stamp',
+            help='The hash stamp to store in the file')
+    
+    argv = sys.argv
+    try:
+        argv.remove('--')
+    except ValueError:
+        pass
+    args, rest = parser.parse_known_args(argv)
+    
+    outfile = args.filepath
+    print("exporting to",outfile)
+    bpy.ops.export.liminalblob(filepath=outfile, stamp=args.stamp)
+    bpy.ops.wm.quit_blender()
+
+if __name__ == '__main__':
+    run()

          
A => liminal_legacy/liminal/data/mpc.py +374 -0
@@ 0,0 1,374 @@ 
+"""marshal based RPC server. loosely based off jpc.""" 
+
+import threading
+import logging
+import inspect
+import socket
+import sys
+
+try:
+    import thread
+except:
+    import _thread as thread
+
+ISPYPY = "__pypy__" in sys.modules
+ISPY3K = sys.version_info[0] >= 3
+
+if sys.platform == 'win32':
+    import pickle as marshal
+else:
+    import marshal
+    
+if ISPY3K:
+    from io import BytesIO as StringIO
+else:
+    from StringIO import StringIO
+
+dumps = lambda v: marshal.dumps(v, 2)
+if ISPY3K:
+    def loads(s):
+        d = marshal.loads(s)
+        return dict([(key.decode('utf-8'),value) for key,value in d.items()])
+else:    
+    loads = lambda s: marshal.loads(s)
+
+LOOPBACK = '127.0.0.1'
+DEFAULT_PORT = 52431
+BUFFER_SIZE = 4096
+
+MAX_THREADS = 128
+
+class ServerError(Exception):
+    """Wrap server errors by proxy."""
+
+
+
+class EofError(Exception):
+    """Socket end of file."""
+
+
+
+class BaseHandler(object):
+    """
+    Handle incomming requests.
+    Client can call public methods of derived classes.
+    """
+
+    def __init__(self, addr=None):
+        self._addr = addr
+        self._methods = {}
+
+
+    def _close(self):
+        self._methods = {}
+
+
+    def _get_method(self, name):
+        """
+        Get public method.
+        Verify attribute is public method and use cache for performance.
+        """
+
+        m = self._methods.get(name, None)
+        if m is not None:
+            return m
+
+        if name.startswith('_'):
+            logging.warning('Attempt to get non-public, attribute=%s.', name)
+            raise ValueError(name)
+
+        m = getattr(self, name)
+        if not inspect.ismethod(m):
+            logging.warning('Attempt to get non-method, attribute=%s.', name)
+            raise ValueError(name)
+
+        self._methods[name] = m
+
+        return m
+
+
+
+class EchoHandler(BaseHandler):
+    """Echo back call arguments for debugging."""
+
+    def echo(self, *args, **kwargs):
+        return {'*args': args, '**kwargs': kwargs}
+
+
+
+class ExampleHandler(BaseHandler):
+    """
+    Demonstrate handler inheritance.
+    Start server with: start_server(handler=ExampleHandler)
+    Client calls server with: Proxy(connection).add(...)
+    """
+
+    def add(self, x, y):
+        return x + y
+
+
+
+class Connection(object):
+    """Wrap socket with buffered read and length prefix for data."""
+
+    def __init__(self, conn):
+        self._conn = conn
+
+    def __getattr__(self, name):
+        """Delegate attributes of socket."""
+
+        return getattr(self._conn, name)
+
+
+    def close(self):
+        """Shut down and close socket."""
+
+        try:
+            self._conn.shutdown(socket.SHUT_RDWR)
+        except socket.error:
+            pass
+
+        self._conn.close()
+        
+
+    def write(self, data):
+        """Write length prefixed data to socket."""
+
+        length = len(data)
+        l = '%08x' % length
+        if ISPY3K:
+            l = l.encode('utf-8')
+
+        self._conn.sendall(l + data)
+
+
+    def read(self):
+        """Read length prefixed data from socket."""
+
+        length = int(self._read(8), 16)
+        return self._read(length)
+
+   
+    def _read(self, length):
+        buffer = StringIO()
+        readsize = 0
+        while readsize < length:
+            data = self._conn.recv(min(BUFFER_SIZE, length - readsize))
+            if not data:
+                raise EofError(readsize)
+            readsize += len(data)
+            buffer.write(data)
+        return buffer.getvalue()
+
+
+
+g_threads_semaphore = threading.Semaphore(MAX_THREADS)
+
+def threaded(foo):
+    """Run foo using bounded number of threads."""
+
+    def wrapper1(*args, **kwargs):
+        try:
+            foo(*args, **kwargs)
+        finally:
+            g_threads_semaphore.release()
+
+    def wrapper2(*args, **kwargs):
+        g_threads_semaphore.acquire()
+        thread.start_new_thread(wrapper1, args, kwargs)
+
+    return wrapper2
+
+
+
+def _serve_connection(conn, addr, handler_factory):
+    """
+    Serve acceptted connection.
+    Should be used in the context of a threaded server, see 
+    threaded_connection(), or fork server (not implemented here).
+    """
+
+    logging.info('Enter, addr=%s.', addr)
+
+    c = Connection(conn)
+
+    try:
+        #
+        # Instantiate handler for the lifetime of the connection,
+        # making it possible to manage a state between calls.
+        #
+        handler = handler_factory(addr)
+
+        try:
+            while True:
+                data = c.read()
+                
+                response = _dispatch(handler, data)
+                if response is None:
+                    continue
+                
+                c.write(response)
+
+        except EofError:
+            logging.debug('Caught end of file, error=%r.', sys.exc_info()[1])
+
+    finally:
+        c.close()
+        handler._close()
+
+
+
+threaded_connection = threaded(_serve_connection)
+
+def _dispatch(handler, data):
+    """
+    Dispatch call to handler.
+    Notifications arrive with no ID and get no response.
+    """
+
+    try:    
+        id = None
+        work_item = loads(data)
+        id = work_item.get('id', None)
+
+        name = work_item['method']
+        foo = handler._get_method(name)
+
+        args = work_item['params']
+        kwargs = work_item.get('kwargs', {})
+
+        result = foo(*args, **kwargs)
+        if id is not None:
+            return dumps({'result': result, 'error': None, 'id': id})
+    except EofError:
+        # re-raise
+        raise
+    except Exception:
+        logging.warning('Caught exception raised by callable.', exc_info=True)
+        if id is not None:
+            return dumps({'result': None, 'error': repr(sys.exc_info()[1]), 'id': id})
+
+
+
+def start_server(host=LOOPBACK, port=DEFAULT_PORT, on_accept=threaded_connection, handler=EchoHandler, quit_check=lambda: False):
+    """Start server."""
+
+    logging.info('Enter, handler=%r, port=%d, host=%s.', handler, port, host)
+
+    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+    s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+
+    try:
+        s.bind((host, port))
+        s.listen(5)
+        s.settimeout(1)
+
+        while not quit_check():
+            try:
+                conn, addr = s.accept()
+                logging.info('Accepted connection from %s.', addr)
+                on_accept(conn, addr, handler)
+            except socket.timeout:
+                pass
+        logging.info("Shutting down...")
+    finally:
+        s.shutdown(socket.SHUT_RDWR)
+        s.close()
+
+
+
+def connect(host=LOOPBACK, port=DEFAULT_PORT, connection_type=Connection):
+    """Connect to server."""
+    if sys.platform != 'win32':    
+        assert ISPYPY or ISPY3K, 'mpc requires pypy or python 3 to run correctly.'
+
+    logging.info('Enter, host=%s, port=%d.', host, port)
+
+    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+    s.connect((host, port))
+
+    return connection_type(s)
+
+
+
+class Proxy(object):
+    """
+    Proxy methods of server handler.
+    
+    Call Proxy(connection).foo(*args, **kwargs) to invoke method
+    handler.foo(*args, **kwargs) of server handler.
+    """
+
+    def __init__(self, conn):
+        self._conn = conn
+        self._id = 1
+
+
+    def _proxy(self, async, name, args, kwargs):
+        """
+        Call method on server.
+        Asynchhronous calls omit ID and do not wait for response.
+        """
+       
+        d = {
+            'method': name,
+            'params': args,
+        }
+
+        if not async:
+            d['id'] = self._id
+            self._id += 1
+
+        if len(kwargs) > 0:
+            d['kwargs'] = kwargs
+        
+        data = dumps(d)
+
+        self._conn.write(data)
+        if async:
+            return
+
+        response = self._conn.read()
+
+        r = loads(response)
+        if r.get('error', None) is not None:
+            logging.warning('Error returned by proxy, error=%s.', r['error'])
+            raise ServerError(r['error'])
+
+        if r['id'] != d['id']:
+            logging.error('Received unmatching id, sent=%s, received=%s.', d['id'], r['id'])
+            raise ValueError(r['id'])
+
+        return r['result']
+
+
+    def __getattr__(self, name):
+        """Return proxy version of method."""
+
+        def proxy(*args, **kwargs):
+            """Call method on server synchronously."""
+            return self._proxy(False, name, args, kwargs)
+
+        return proxy
+
+
+
+class Notifier(Proxy):
+    """
+    Proxy methods of server handler, asynchronously.
+    Call Notifier(connection).foo(*args, **kwargs) to invoke method
+    handler.foo(*args, **kwargs) of server handler.
+    """
+
+
+    def __getattr__(self, name):
+        """Return async proxy version of method."""
+
+        def proxy(*args, **kwargs):
+            """Call method on server asynchronously."""
+            return self._proxy(True, name, args, kwargs)
+
+        return proxy
+
+
+

          
A => liminal_legacy/liminal/data/source.py +326 -0
@@ 0,0 1,326 @@ 
+"""
+data sources offer filesystem-like access to an json-like object tree. The
+data source can map local resources such as archives and remote ones such as
+provided by the liminald data server.
+"""
+
+from __future__ import (print_function, division, absolute_import)
+
+import logging
+import time
+import os
+import tarfile
+import marshal
+import sys
+from io import BytesIO
+
+log = logging.getLogger('liminal.source')
+
+thisdir = os.path.dirname(os.path.abspath(__file__))
+
+################################################################################
+
+ARCHIVE_MAGICID = '1.6:'
+
+################################################################################
+
+class DataSource(object):
+    __slots__ = [
+        '__weakref__'
+    ]
+    
+    def getkeys(self, path):
+        return []
+
+    def getitem(self, path):
+        return None
+
+    def setitem(self, path, value):
+        pass
+    
+    def setitems(self, pairs):
+        return [self.setitem(path, value) for (path,value) in pairs]
+    
+    def poll(self):
+        pass
+    
+    def hasitem(self, path):
+        return False
+
+    def fspath(self, path):
+        return path.lstrip('//')
+    
+    def add_search_path(self, path):
+        pass
+
+################################################################################
+
+class FileSource(DataSource):
+    __slots__ = [
+        '_searchpath',
+    ]
+    
+    def __init__(self):
+        self._searchpath = []
+    
+    @property
+    def search_path(self):
+        return self._searchpath
+
+    
+    def fspath(self, path):
+        if not path.startswith('//'):
+            return path
+        cleanpath = path[2:]
+        for searchpath in self._searchpath:
+            fullpath = os.path.join(searchpath, cleanpath)
+            #log.debug("searching for {}".format(fullpath))
+            if os.path.isfile(fullpath):
+                return fullpath
+            if os.path.isdir(fullpath): 
+                return fullpath
+        return path 
+    
+    def add_search_path(self, path):
+        self._searchpath.append(path)
+    
+################################################################################
+
+class DataArchive(DataSource):
+    __slots__ = [
+        '_tree',
+        '_searchpath',
+    ]
+    
+    def __init__(self, filepath = None):
+        self._tree = {}
+        self._searchpath = []
+        if filepath:
+            self.read_archive(filepath)
+        
+    @property
+    def tree(self):
+        return self._tree
+        
+    @property
+    def searchpath(self):
+        return self._searchpath
+        
+    def write_archive(self, filepath):
+        root = self._tree
+        t = time.time()
+        
+        #compression = 'gz'
+        compression = ''
+        
+        log.info("Packing data to {}".format(filepath))
+        with tarfile.open(filepath, 'w:' + compression) as f:
+            def walk_dict(path, obj):
+                log.debug("Entering {}".format(path))
+                keys_added = 0
+                for key,value in obj.items():
+                    subpath = path + '/' + key
+                    if isinstance(value, dict):
+                        walk_dict(subpath, value)
+                    else:
+                        keys_added += 1
+                        data = marshal.dumps(value,2)
+                        tinfo = tarfile.TarInfo(subpath.lstrip('/'))
+                        tinfo.type = tarfile.REGTYPE
+                        tinfo.size = len(data)
+                        f.addfile(tinfo, BytesIO(data))
+                log.debug("Leaving {}: {} keys added".format(path, keys_added))
+            walk_dict('', root)
+        
+        log.info("Packed in {:.3g}s.".format(time.time() - t))
+        
+    @classmethod
+    def get_hash(cls, filepath):
+        if not os.path.isfile(filepath):
+            return None
+        with tarfile.open(filepath, 'r') as f:
+            tinfo = f.getmember('__stamp__/value')
+            value = marshal.loads(f.extractfile(tinfo).read())
+            return value
+        
+    def read_archive(self, filepath):
+        with tarfile.open(filepath, 'r') as f:
+            
+            for tinfo in f.getmembers():
+                root = self._tree
+                name = tinfo.name
+                path,key = self._splitpath(name)
+                
+                for e in self._splitfullpath(path):
+                    root = root.setdefault(e, {})
+                
+                value = marshal.loads(f.extractfile(tinfo).read())
+                root[key] = value
+                
+    def add_search_path(self, path):
+        self._searchpath.append(path)
+
+    def insert_search_path(self, index, path):
+        self._searchpath.insert(index, path)
+    
+    def _splitpath(self, path):
+        try:
+            idx = path.rindex('/')
+        except ValueError:
+            return '',path
+        path,key = path[:idx],path[idx+1:]
+        assert not ('/' in key), "key contains /"
+        return path,key
+    
+    def _splitfullpath(self, path):
+        path = path.lstrip('/')
+        for e in path.split('/'):
+            if not e:
+                continue
+            yield e
+    
+    def _resolve(self, path):
+        root = self._tree
+        for e in self._splitfullpath(path):
+            root = root.get(e, None)
+            if not root:
+                return None
+        return root
+    
+    def getkeys(self, path):
+        item = self._resolve(path)
+        if item and isinstance(item, dict):
+            return item.keys()
+        else:
+            return []
+
+    def hasitem(self, path):
+        path,key = self._splitpath(path)
+        root = self._resolve(path)
+        if root and isinstance(root, dict):
+            return key in root
+        else:
+            return False
+
+    def _getdefaultitem(self, path):
+        root = self._tree
+        pathlist = list(self._splitfullpath(path))
+        for name in pathlist:
+            # key accessor
+            root = root.setdefault(name,{})
+        return root, pathlist
+    
+    def getitem(self, uri):
+        path,key = self._splitpath(uri)
+        root = self._resolve(path)
+        if root and isinstance(root, dict):
+            assert key in root, '{}: {} has no key {}'.format(uri, path, key)
+            return root[key]
+        else:
+            return None
+    
+    def _setitem(self, path, value):
+        #log.debug('setitem("{path}",...)'.format(**locals()))
+        path,key = self._splitpath(path)
+        if key:
+            item,pathlist = self._getdefaultitem(path)        
+            item[key] = value
+            pathlist.append(key)
+        else:
+            assert path in ('/',''),path
+            pathlist = ['','']
+            self._tree = value
+        return '/'.join(pathlist)
+            
+    def setitem(self, path, value):
+        return self._setitem(path, value)
+        
+    def fspath(self, path):
+        if not path.startswith('//'):
+            return path
+        cleanpath = path[2:]
+        for searchpath in self._searchpath:
+            fullpath = os.path.join(searchpath, cleanpath)
+            if os.path.isfile(fullpath):
+                return fullpath
+            if os.path.isdir(fullpath): 
+                return fullpath
+        return path 
+
+################################################################################
+
+def compile_blend(sourcepath, destpath):
+    import subprocess
+    import hashlib
+    
+    def md5_for_file(f, block_size=2**20):
+        md5 = hashlib.md5()
+        while True:
+            data = f.read(block_size)
+            if not data:
+                break
+            md5.update(data)
+        return md5.hexdigest()
+
+    def find_blender_path():
+        bin_name = 'blender'
+        if sys.platform == 'win32':
+            bin_name = bin_name + '.exe'
+        # find pypy in path
+        PATH = os.environ['PATH'].split(os.pathsep)
+        for dirpath in PATH:
+            fullpath = os.path.join(dirpath, bin_name)
+            if os.path.isfile(fullpath):
+                return fullpath
+    
+    sourcepath = os.path.abspath(sourcepath)
+    destpath = os.path.abspath(destpath)
+    
+    assert os.path.isfile(sourcepath)        
+    with open(sourcepath, 'rb') as f: 
+        srchash = ARCHIVE_MAGICID + md5_for_file(f)
+        
+    # aptana injects this, remove this for subprocess call
+    os.environ.pop('PYTHONPATH',None)
+    
+    if os.path.isfile(destpath):
+        dsthash = DataArchive.get_hash(destpath)
+        if srchash == dsthash:
+            return
+        os.remove(destpath)
+        
+    blenderpath = find_blender_path()
+    assert blenderpath, 'blender not found in PATH'
+    compilescript = os.path.join(thisdir, 'export_blendc.py')
+    assert os.path.isfile(compilescript)
+        
+    args = [
+        blenderpath,
+        sourcepath,
+        '-P', compilescript,
+        '--',
+        '--stamp', srchash,
+        '--liminalblob', destpath,
+    ]
+    if 1:
+        retcode = subprocess.call(args)
+        assert retcode in (0,234)
+    else:
+        with open(os.devnull, 'w') as fp: 
+            subprocess.check_call(args, stdout=fp, stderr=fp)
+
+def load_archive(filepath):
+    from ..engine.archive import resolve_path
+
+    sourcepath = resolve_path(filepath)
+    
+    if os.path.isfile(sourcepath): # source file exists
+        cachepath = os.path.splitext(sourcepath)[0] + '.blendc'
+        compile_blend(sourcepath, cachepath)
+    else:
+        cachepath = resolve_path(os.path.splitext(filepath)[0] + '.blendc')
+        assert os.path.isfile(cachepath)
+    return DataArchive(cachepath)
+
+################################################################################
+

          
A => liminal_legacy/liminal/engine/__init__.py +26 -0
@@ 0,0 1,26 @@ 
+from __future__ import (print_function, division, absolute_import)
+
+import traceback
+import math
+import logging
+
+from . import core
+from . import system
+from . import camera
+from . import interface
+from . import logic
+from . import material
+from . import mesh
+from . import actor
+from . import render
+from . import shader
+from . import texture
+from . import input
+from . import font
+from . import audio
+from . import ppfx
+from . import archive
+from . import timing
+from ..utils import TODO
+
+################################################################################

          
A => liminal_legacy/liminal/engine/actor.py +169 -0
@@ 0,0 1,169 @@ 
+from __future__ import (print_function, division, absolute_import)
+
+import weakref
+import logging
+import traceback
+
+from glm import mat4, vec3, rotate, X_AXIS, Y_AXIS, Z_AXIS
+from glue import callproxy, callset
+
+from .render import ModelAttribs, RNode, RJCMD_DRAW
+from .core import RenderManager
+from .physics import Geom, Sphere
+
+################################################################################
+
+log = logging.getLogger('liminal.object')
+
+################################################################################
+
+class RActor(RNode):
+    def __init__(self, actor):
+        RNode.__init__(self)
+        self.actor = actor
+        self.name = actor.name
+        
+    def process(self, batch, jobs):
+        RNode.process(self, batch, jobs)
+        for job in jobs:
+            if job.key.command != RJCMD_DRAW: continue
+            job.draw.ubo_model = self.actor.model_attribs    
+
+################################################################################
+
+class Actor(RNode):
+    attributes = [
+        '_geom',
+        '_last_model_matrix',
+        '_model_attribs',
+    ]
+    
+    def __init__(self, geom):
+        RNode.__init__(self)
+        assert isinstance(geom, Geom), geom
+        self._geom = geom
+        self._last_model_matrix = None
+        self._model_attribs = None
+
+    def process(self, batch, jobs):
+        RNode.process(self, batch, jobs)
+        self.invalidate_uniforms()
+        for job in jobs:
+            if job.key.command != RJCMD_DRAW: continue
+            job.draw.ubo_model = self.model_attribs
+        
+    TRANSFORM_SCALE = vec3(1,1,1)
+
+    @property
+    def geom(self):
+        return self._geom
+    @property
+    def parent(self):
+        raise AttributeError, "no longer valid"
+    
+    @property
+    def body(self):
+        return self._geom.body
+    @body.setter
+    def body(self, value):
+        if self._geom.body:
+            self._geom.body.moved_callback.discard(self.on_moved)
+        self._geom.body = value
+        if value is not None:
+            value.moved_callback.add(callproxy(self.on_moved))
+    
+    @property
+    def world_transform(self):
+        raise AttributeError, "no longer valid"
+    @property
+    def world_position(self):
+        raise AttributeError, "no longer valid"
+    @property
+    def world_orientation(self):
+        raise AttributeError, "no longer valid"
+    @property
+    def world_scale(self):
+        raise AttributeError, "no longer valid"
+
+    @property
+    def scale(self):
+        return self._geom.scale
+    @scale.setter
+    def scale(self, value):
+        self._geom.scale = value
+        self.invalidate_uniforms()
+    
+    @property
+    def position(self):
+        return self._geom.position        
+    @position.setter
+    def position(self, value):        
+        self._geom.position = value
+        self.invalidate_uniforms()
+
+    @property
+    def orientation(self):
+        return self._geom.orientation
+    @orientation.setter
+    def orientation(self, value):
+        self._geom.orientation = value
+        self.invalidate_uniforms()
+
+    def get_position_scale_orientation(self):
+        return self._geom.get_position_scale_orientation()
+
+    def set_position_scale_orientation(self, p, s, o):
+        self._geom.set_position_scale_orientation(p,s,o)
+        self.invalidate_uniforms()
+    
+    @property
+    def transform(self):
+        return self._get_transform_matrix()
+    @transform.setter
+    def transform(self, value):
+        self._set_transform_matrix(value)
+
+    def _get_transform_matrix(self):
+        p,s,o = self.get_position_scale_orientation()
+        return mat4.compose(p,s,o)
+
+    def _set_transform_matrix(self, value):
+        p = value.get_translation()
+        s,o = value.get_scale_rotation()
+        self.set_position_scale_orientation(p,s,o)
+    
+    def invalidate_uniforms(self):
+        RenderManager().invalidate_uniforms(self)
+            
+    def on_moved(self):
+        self.invalidate_uniforms()
+        
+    def write_model_attribs(self, attrib, data):
+        model_mtx = self.transform
+        
+        last_model_matrix = self._last_model_matrix
+        if last_model_matrix is None:
+            last_model_matrix = model_mtx
+        self._last_model_matrix = model_mtx
+        
+        data.mtx_model = model_mtx._ptr[0]
+        data.mtx_last_model = last_model_matrix._ptr[0]
+        data.mtx_inv_model = model_mtx.inverse()._ptr[0]
+        
+    @property
+    def model_attribs(self):
+        attribs = self._model_attribs
+        if attribs is None:
+            attribs = ModelAttribs()
+            self._model_attribs = attribs 
+        return attribs
+        
+    def update_uniforms(self):
+        # non-renderable?
+        # if attribs is None, implement your own update_uniforms()
+        attribs = self.model_attribs
+        with attribs.map_write() as data:
+            self.write_model_attribs(attribs, data)
+
+################################################################################
+    
  No newline at end of file

          
A => liminal_legacy/liminal/engine/archive.py +43 -0
@@ 0,0 1,43 @@ 
+from __future__ import (print_function, division, absolute_import)
+
+import os
+import logging
+
+from glue import Singleton
+
+from ..data.source import FileSource
+from .config import Config
+
+################################################################################
+
+log = logging.getLogger('liminal.archive')
+THISDIR = os.path.abspath(os.path.dirname(__file__))
+
+################################################################################
+
+class ArchiveManager(FileSource):
+    __metaclass__ = Singleton
+    
+    __slots__ = []
+    
+    def __init__(self):
+        FileSource.__init__(self)
+        assert Config().base_path, 'Config not initialized yet'
+        self.add_search_path(Config().base_path)
+        self.add_search_path(os.path.abspath(
+            os.path.join(THISDIR, '..', 'assets')))
+        
+    def resolve_path(self, path):
+        return self.fspath(path)
+
+    def load_text(self, name):
+        with self.open(name) as f:
+            return f.read()
+
+    def open(self, name, mode = 'r'):
+        return open(self.resolve_path(name), mode)
+
+################################################################################
+
+def resolve_path(path):
+    return ArchiveManager().resolve_path(path)

          
A => liminal_legacy/liminal/engine/audio.py +615 -0
@@ 0,0 1,615 @@ 
+from __future__ import (print_function, division, absolute_import)
+
+import os
+import logging
+import thread
+import time
+import weakref
+import wave
+
+from glue import Singleton, callproxy
+from glm import vec3
+
+from al import (alBuffer, AL_FORMAT_STEREO16, AL_FORMAT_MONO16, #@UnresolvedImport
+    alBufferData, alSource, alSourcef, alSource3f, alSourcei, AL_PITCH, #@UnresolvedImport
+    AL_GAIN, AL_POSITION, AL_VELOCITY, AL_LOOPING, AL_TRUE, AL_BUFFER,
+    alSourcePlay, AL_FALSE, alGetSourcef, alGetSourcei,
+    AL_PLAYING, AL_PAUSED, AL_STOPPED, AL_SOURCE_STATE, AL_INITIAL,
+    alSourceStop, AL_SEC_OFFSET, alcOpenDevice, alcCreateContext, 
+    alcMakeContextCurrent, alcDestroyContext, alcCloseDevice, 
+    alSourceQueueBuffers, alSourceUnqueueBuffers,
+    AL_DIRECTION, AL_ROLLOFF_FACTOR, AL_SOURCE_RELATIVE,
+    AL_BUFFERS_QUEUED, AL_BUFFERS_PROCESSED, ALC_FREQUENCY, ALC_MONO_SOURCES, 
+    ALC_STEREO_SOURCES, alListener3f, AL_ORIENTATION, alListenerfv,
+    alGetBufferi) 
+
+from stbvorbis import Vorbis
+
+from .interface import Named
+from .archive import resolve_path
+from al._al import AL_FREQUENCY, AL_BITS, AL_CHANNELS, AL_SIZE, AL_MIN_GAIN,\
+    AL_MAX_GAIN, AL_MAX_DISTANCE, AL_CONE_OUTER_GAIN, AL_CONE_INNER_ANGLE,\
+    AL_CONE_OUTER_ANGLE, AL_REFERENCE_DISTANCE, AL_VELOCITY, AL_DIRECTION,\
+    AL_NONE, AL_SOURCE_TYPE, AL_UNDETERMINED, AL_STATIC, AL_STREAMING,\
+    alSourcePause, alSourceRewind, AL_BYTE_OFFSET, AL_EXPONENT_DISTANCE_CLAMPED,\
+    AL_EXPONENT_DISTANCE, AL_LINEAR_DISTANCE_CLAMPED, AL_LINEAR_DISTANCE,\
+    AL_INVERSE_DISTANCE_CLAMPED, AL_INVERSE_DISTANCE, alGetInteger,\
+    AL_DISTANCE_MODEL, alDistanceModel, alDopplerFactor, alGetFloat,\
+    AL_DOPPLER_FACTOR, AL_SPEED_OF_SOUND, alSpeedOfSound
+from al.al import alGetSourcefv
+from .timing import TimeManager
+
+################################################################################
+
+log = logging.getLogger('liminal.audio')
+
+################################################################################
+
+class AudioError(Exception):
+    pass
+
+class SoundFormat:
+    Mono16 = AL_FORMAT_MONO16
+    Stereo16 = AL_FORMAT_STEREO16
+
+class SourceType:
+    Undetermined = AL_UNDETERMINED
+    Static = AL_STATIC
+    Streaming = AL_STREAMING
+
+class SourceState:
+    Initial = AL_INITIAL
+    Playing = AL_PLAYING
+    Paused = AL_PAUSED
+    Stopped = AL_STOPPED
+    
+class DistanceModel:
+    Disabled = AL_NONE
+    InverseDistance = AL_INVERSE_DISTANCE
+    InverseDistanceClamped = AL_INVERSE_DISTANCE_CLAMPED
+    LinearDistance = AL_LINEAR_DISTANCE
+    LinearDistanceClamped = AL_LINEAR_DISTANCE_CLAMPED
+    ExponentDistance = AL_EXPONENT_DISTANCE
+    ExponentDistanceClamped = AL_EXPONENT_DISTANCE_CLAMPED
+    Default = InverseDistanceClamped # D3D model
+
+###############################################################################
+
+class Source(Named, alSource):
+    __slots__ = [
+        '_sound',
+    ]
+
+    _users = weakref.WeakValueDictionary()
+    
+    def __init__(self):
+        Named.__init__(self)
+        alSource.__init__(self)
+        self._sound = None
+        self._users[int(self)] = self 
+        
+    def update_from_actor(self, actor):
+        self.position = actor.world_position
+        self.direction = actor.world_orientation.col1()
+        velocity = actor.body.linear_velocity
+        if velocity is None:
+            velocity = vec3(0,0,0)
+        self.velocity = velocity        
+        
+    @property
+    def pitch(self):
+        return alGetSourcef(self, AL_PITCH)
+    @pitch.setter
+    def pitch(self, value):
+        alSourcef(self, AL_PITCH, value)
+        
+    @property
+    def gain(self):
+        return alGetSourcef(self, AL_GAIN)
+    @gain.setter
+    def gain(self, value):
+        alSourcef(self, AL_GAIN, value)
+
+    @property
+    def min_gain(self):
+        return alGetSourcef(self, AL_MIN_GAIN)
+    @min_gain.setter
+    def min_gain(self, value):
+        alSourcef(self, AL_MIN_GAIN, value)
+        
+    @property
+    def max_gain(self):
+        return alGetSourcef(self, AL_MAX_GAIN)
+    @max_gain.setter
+    def max_gain(self, value):
+        alSourcef(self, AL_MAX_GAIN, value)
+        
+    @property
+    def max_distance(self):
+        return alGetSourcef(self, AL_MAX_DISTANCE)
+    @max_distance.setter
+    def max_distance(self, value):
+        alSourcef(self, AL_MAX_DISTANCE, value)
+
+    @property
+    def rolloff_factor(self):
+        return alGetSourcef(self, AL_ROLLOFF_FACTOR)
+    @rolloff_factor.setter
+    def rolloff_factor(self, value):
+        alSourcef(self, AL_ROLLOFF_FACTOR, value)
+        
+    @property
+    def cone_outer_gain(self):
+        return alGetSourcef(self, AL_CONE_OUTER_GAIN)
+    @cone_outer_gain.setter
+    def cone_outer_gain(self, value):
+        alSourcef(self, AL_CONE_OUTER_GAIN, value)
+        
+    @property
+    def cone_inner_angle(self):
+        return alGetSourcef(self, AL_CONE_INNER_ANGLE)
+    @cone_inner_angle.setter
+    def cone_inner_angle(self, value):
+        alSourcef(self, AL_CONE_INNER_ANGLE, value)
+        
+    @property
+    def cone_outer_angle(self):
+        return alGetSourcef(self, AL_CONE_OUTER_ANGLE)
+    @cone_outer_angle.setter
+    def cone_outer_angle(self, value):
+        alSourcef(self, AL_CONE_OUTER_ANGLE, value)
+    
+    @property
+    def reference_distance(self):
+        return alGetSourcef(self, AL_REFERENCE_DISTANCE)
+    @reference_distance.setter
+    def reference_distance(self, value):
+        alSourcef(self, AL_REFERENCE_DISTANCE, value)
+    
+    @property
+    def position(self):
+        return vec3(*alGetSourcefv(self, AL_POSITION, 3))
+    @position.setter
+    def position(self, value):
+        alSource3f(self, AL_POSITION, *value.to_tuple())
+    
+    @property
+    def velocity(self):
+        return vec3(*alGetSourcefv(self, AL_VELOCITY, 3))
+    @velocity.setter
+    def velocity(self, value):
+        alSource3f(self, AL_VELOCITY, *value.to_tuple())
+    
+    @property
+    def direction(self):
+        return vec3(*alGetSourcefv(self, AL_DIRECTION, 3))
+    @direction.setter
+    def direction(self, value):
+        alSource3f(self, AL_DIRECTION, *value.to_tuple())
+    
+    @property
+    def relative(self):
+        return alGetSourcei(self, AL_SOURCE_RELATIVE)
+    @relative.setter
+    def relative(self, value):
+        alSourcei(self, AL_SOURCE_RELATIVE, value)
+        
+    @property
+    def sound(self):
+        return self._sound
+    @sound.setter
+    def sound(self, value):
+        self._sound = value
+        self.buffer = value
+        
+    @property
+    def buffer(self):
+        ptr = alGetSourcei(self, AL_BUFFER)
+        if ptr is AL_NONE:
+            return None
+        return Sound.resolve(ptr)
+    @buffer.setter
+    def buffer(self, value):
+        if value is None:
+            value = AL_NONE
+        assert isinstance(value, alBuffer)
+        alSourcei(self, AL_BUFFER, value)
+    
+    @property
+    def state(self):
+        return alGetSourcei(self, AL_SOURCE_STATE)
+    @state.setter
+    def state(self, value):
+        alSourcei(self, AL_SOURCE_STATE, value)
+        
+    @property
+    def type(self):
+        return alGetSourcei(self, AL_SOURCE_TYPE)
+    @type.setter
+    def type(self, value):
+        alSourcei(self, AL_SOURCE_TYPE, value)
+        
+    @property
+    def buffers_queued(self):
+        return alGetSourcei(self, AL_BUFFERS_QUEUED)
+    
+    @property
+    def buffers_processed(self):
+        return alGetSourcei(self, AL_BUFFERS_PROCESSED)
+    
+    @property
+    def byte_offset(self):
+        return alGetSourcei(self, AL_BYTE_OFFSET)
+    
+    @property
+    def sec_offset(self):
+        return alGetSourcef(self, AL_SEC_OFFSET)
+    @sec_offset.setter
+    def sec_offset(self, value):
+        alSourcef(self, AL_SEC_OFFSET, value)
+
+    @property
+    def looping(self):
+        return alGetSourcei(self, AL_LOOPING)
+    @looping.setter
+    def looping(self, value):
+        alSourcei(self, AL_LOOPING, value)
+
+    def play(self):
+        alSourcePlay(self)
+        
+    def pause(self):
+        alSourcePause(self)
+        
+    def stop(self):
+        alSourceStop(self)
+        
+    def rewind(self):
+        alSourceRewind(self)
+
+################################################################################
+
+class Sound(Named, alBuffer):
+    __slots__ = [
+    ]
+    
+    _users = weakref.WeakValueDictionary()
+    
+    def __init__(self):
+        Named.__init__(self)
+        alBuffer.__init__(self)
+        self._users[int(self)] = self 
+        
+    @classmethod
+    def resolve(cls, ptr):
+        return cls._users.get(ptr, None)
+        
+    def Data(self, audio_format, samples, sample_rate):
+        alBufferData(self, audio_format, samples, sample_rate)
+    
+    @property
+    def frequency(self):
+        return alGetBufferi(self, AL_FREQUENCY)
+    
+    @property
+    def bits(self):
+        return alGetBufferi(self, AL_BITS)
+    
+    @property
+    def channels(self):
+        return alGetBufferi(self, AL_CHANNELS)
+    
+    @property
+    def size(self): # in bytes
+        return alGetBufferi(self, AL_SIZE)
+    
+    @property
+    def count(self): # in samples
+        return self.size * 8 // (self.channels * self.bits)
+    
+    @property
+    def duration(self): # in seconds
+        return self.count / self.frequency
+    
+    @classmethod
+    def format_from_channels(cls, channels):
+        if channels == 1:
+            return SoundFormat.Mono16
+        elif channels == 2:
+            return SoundFormat.Stereo16
+        else:
+            raise ValueError, \
+                'number of channels ({}) not supported'.format(channels)
+    
+    @classmethod
+    def from_buffer16(cls, channels, data, sample_rate = 44100):
+        sound = cls()
+        sound.Data(cls.format_from_channels(channels), data, sample_rate)
+        return sound
+    
+    @classmethod
+    def from_path(cls, path):
+        path = resolve_path(path)
+        log.debug("Loading sound from {0}".format(path))
+        assert os.path.isfile(path), "Soundfile {0} does not exist.".format(path)
+        
+        ext = os.path.splitext(path)[1].lower()
+        if ext == '.ogg':
+            vbuffer = Vorbis.open_filename(path)
+            info = vbuffer.get_info()
+            assert info.channels > 0 and info.channels <= 2
+            samples = vbuffer.get_samples_short_interleaved(info.channels,
+                vbuffer.stream_length_in_samples())
+            channels = info.channels
+            sample_rate = info.sample_rate
+        elif ext == '.wav':
+            # load wave file into buffer
+            f = wave.open(path, 'rb')
+            width = f.getsampwidth()
+            assert width == 2, '16-bit sample expected.'
+            assert f.getcomptype() == 'NONE', 'compression not supported.'
+            channels = f.getnchannels()
+            sample_rate = f.getframerate()
+            sample_count = f.getnframes()
+            samples = f.readframes(sample_count)
+            assert len(samples) == sample_count*width*channels
+            assert len(samples)%2 == 0
+            f.close()
+        else:      
+            raise AudioError, \
+                'Unsupported file format.'
+        sound = cls.from_buffer16(channels, samples, sample_rate)
+        sound.name = os.path.basename(path)
+        return sound
+
+################################################################################
+    
+class Listener(Named):
+    __slots__ = [
+    ]
+    
+    def __init__(self):
+        Named.__init__(self)
+
+    def set(self):
+        AudioManager().listener = self
+        
+    def update_from_values(self, position, orientation, velocity):
+        if AudioManager().listener is not self:
+            return
+        alListener3f(AL_POSITION, *position.to_tuple())
+        at = orientation.col1().mul_f(-1).to_tuple()     
+        up = orientation.col2().mul_f(-1).to_tuple()
+        alListenerfv(AL_ORIENTATION, at + up)
+        alListener3f(AL_VELOCITY, *velocity.to_tuple())
+
+################################################################################
+
+class Channel(Named):
+    __slots__ = [
+        '_sources',
+        '_next_source_idx',
+    ]
+    
+    def __init__(self, voices = 1):
+        Named.__init__(self)
+        self._sources = [Source() for i in range(voices)]
+        self._next_source_idx = 0
+        AudioManager().channels.add(self)
+        
+    def _get_next_source(self):
+        for source in self._sources:
+            if source.state != SourceState.Playing:
+                return source
+        idx = self._next_source_idx
+        self._next_source_idx = (idx+1) % len(self._sources)
+        return self._sources[idx]
+    
+    def stop_all(self):
+        for source in self._sources:
+            source.stop()
+        
+    def play_at(self, actor, sound):
+        source = self._get_next_source()
+        source.stop()
+        source.sound = sound
+        source.update_from_actor(actor)
+        source.play()
+
+################################################################################
+
+class AudioManager(object):
+    __metaclass__ = Singleton
+    
+    __slots__ = [
+        '__weakref__',
+        '_aldevice',
+        '_alcontext',
+        '_last_position',
+        '_listener',
+        '_channels',
+    ]
+    
+    def __init__(self):
+        self._aldevice = None
+        self._alcontext = None
+        self._listener = None
+        self._channels = weakref.WeakSet()
+        
+    @property
+    def channels(self):
+        return self._channels
+        
+    def on_frame(self):
+        if self._alcontext is None:
+            return
+        self.update_listener()    
+        
+    def update_listener(self):
+        if self._alcontext is None:
+            return
+        listener = self.listener
+        if listener is None:
+            # default setup
+            alListener3f(AL_POSITION, 0, 0, 0)
+            alListener3f(AL_VELOCITY, 0, 0, 0)
+            alListenerfv(AL_ORIENTATION, [0, 0, -1, 0, 1, 0])
+            return
+        
+        position = listener.world_position
+        orientation = listener.world_orientation
+        velocity = listener.body.linear_velocity
+        if velocity is None:
+            last_position = self._last_position 
+            if last_position is not None:
+                tm = TimeManager()                
+                velocity = position.sub(last_position).mul_f(tm.avg_framerate)
+            else:
+                velocity = vec3(0,0,0)
+            self._last_position = position
+        
+        alListener3f(AL_POSITION, *position.to_tuple())
+        at = orientation.col1().mul_f(-1).to_tuple()     
+        up = orientation.col2().mul_f(-1).to_tuple()
+        alListenerfv(AL_ORIENTATION, at + up)
+        alListener3f(AL_VELOCITY, *velocity.to_tuple())        
+
+        
+    @property
+    def listener(self):
+        return self._listener and self._listener()
+    @listener.setter
+    def listener(self, value):
+        self._listener = weakref.ref(value)
+        self._last_position = None
+        #self.update_listener()
+        
+    @property
+    def distance_model(self):
+        return alGetInteger(AL_DISTANCE_MODEL)
+    @distance_model.setter
+    def distance_model(self, value):
+        alDistanceModel(value)
+        
+    @property
+    def doppler_factor(self):
+        return alGetFloat(AL_DOPPLER_FACTOR)
+    @doppler_factor.setter
+    def doppler_factor(self, value):
+        # default is 1
+        alDopplerFactor(value)
+        
+    @property
+    def speed_of_sound(self):
+        return alGetFloat(AL_SPEED_OF_SOUND)
+    @speed_of_sound.setter
+    def speed_of_sound(self, value):
+        # default is 343.3
+        alSpeedOfSound(value)
+        
+    def destroy(self):
+        if not (self._alcontext is None):
+            log.info("Destroying AL context...")
+            alcDestroyContext(self._alcontext)
+        if not (self._aldevice is None):
+            log.info("Closing AL device...")
+            alcCloseDevice(self._aldevice)
+        
+    def init(self):
+        if self._aldevice:
+            return
+        log.info("Opening AL device...")
+        self._aldevice = alcOpenDevice(None)
+        assert self._alcontext is None
+        log.info("Creating AL context...")
+        self._alcontext = alcCreateContext(self._aldevice, [
+            ALC_FREQUENCY, 44100,
+            #ALC_REFRESH, 60,
+            #ALC_SYNC, 1,
+            ALC_MONO_SOURCES, 16,
+            ALC_STEREO_SOURCES, 8,
+        ])
+        result = alcMakeContextCurrent(self._alcontext)
+        assert result == AL_TRUE
+        from .system import System
+        System.event_frame.add(self.on_frame)
+
+################################################################################
+
+class Stream(object):
+    __slots__ = [
+        '_source',
+        '_buffers'
+    ]
+    
+    def __init__(self, buffercount):
+        self._buffers = [alBuffer() for i in range(buffercount)]
+        
+        source = alSource()
+        alSource3f(source, AL_POSITION, 0, 0, 0)
+        alSource3f(source, AL_VELOCITY, 0, 0, 0)
+        alSource3f(source, AL_DIRECTION, 0, 0, 0)
+        alSourcef(source, AL_ROLLOFF_FACTOR, 0)
+        alSourcei(source, AL_SOURCE_RELATIVE, AL_TRUE)
+        self._source = source
+        self._thread = None
+        self._exit_thread = False
+    
+    def thread_update(self):
+        while not self._exit_thread:
+            time.sleep(0.05)
+            self.update()
+    
+    def start_thread(self):
+        if self._thread:
+            return
+        self._thread = thread.start_new_thread(self.thread_update, ())
+        from .system import System
+        System().event_destroy.add(callproxy(self.stop_thread))
+    
+    def stop_thread(self):
+        if self._thread is None:
+            return
+        self._exit_thread = True
+        self._thread = None
+        time.sleep(0.2)
+    
+    def play(self):
+        for buffer in self._buffers:
+            self.stream(buffer)
+        alSourceQueueBuffers(self._source, self._buffers)
+        alSourcePlay(self._source)
+        
+    def stop(self):
+        alSourceStop(self._source)
+        self.empty()       
+            
+    def empty(self):
+        queued = alGetSourcei(self._source, AL_BUFFERS_QUEUED)
+        for i in range(queued):
+            alSourceUnqueueBuffers(self._source, 1)        
+
+    def update(self):
+        if (alGetSourcei(self._source, AL_SOURCE_STATE) == AL_STOPPED):
+            log.warn("Buffer underrun, restarting Stream")
+            self.empty()
+            self.play()
+            return True
+        
+        active = True
+        processed = alGetSourcei(self._source, AL_BUFFERS_PROCESSED)
+        for i in range(processed):
+            buffer = alSourceUnqueueBuffers(self._source, 1)[0]
+            active = self.stream(buffer)
+            alSourceQueueBuffers(self._source, [buffer])
+        return active  
+    
+    def stream(self, buffer):
+        data = self.generate()
+        alBufferData(buffer, AL_FORMAT_STEREO16, data, 44100)
+        return False
+        
+    def generate(self):
+        raise NotImplementedError("implement this function")
+
+################################################################################

          
A => liminal_legacy/liminal/engine/basebuffer.py +96 -0
@@ 0,0 1,96 @@ 
+
+from gl import (glBuffer, glBindBuffer, GL_DYNAMIC_DRAW,  #@UnresolvedImport
+    GL_MAP_INVALIDATE_BUFFER_BIT,
+    GL_MAP_WRITE_BIT, GL_MAP_UNSYNCHRONIZED_BIT, glUnmapBuffer,
+    glMapBufferRange, glBufferData, GL_MAP_READ_BIT, GL_TRUE,
+    glBufferSubData)
+
+from .interface import Named
+
+################################################################################
+
+class Buffer(glBuffer, Named):
+    __slots__ = [
+        '_size',
+    ]
+    
+    class MapBuffer(object):
+        def __init__(self, map_buffer, data):
+            self.map_buffer = map_buffer
+            self.data = data
+        
+        def __enter__(self):
+            return self.data
+        
+        def __exit__(self, type_, value, tb):
+            self.map_buffer.unmap()
+    
+    TARGET = None
+    USAGE = None
+    
+    def __init__(self):
+        Named.__init__(self)
+        glBuffer.__init__(self)
+        self._size = 0
+        
+    @property
+    def size(self):
+        return self._size
+    @size.setter
+    def size(self, value):
+        if value is self._size:
+            return
+        self._size = value
+        self.reallocate()
+        
+    @classmethod
+    def from_size(cls, size):
+        mesh = cls()
+        mesh.size = size
+        return mesh
+    
+    def bind(self):
+        glBindBuffer(self.TARGET, self)
+        
+    @classmethod
+    def unbind(cls):
+        glBindBuffer(cls.TARGET, 0)
+        
+    def map(self, offset, length, flags):
+        assert offset+length <= self._size
+        self.bind()
+        data = glMapBufferRange(self.TARGET, offset, length, flags)
+        return self.MapBuffer(self, data)
+        
+    def map_read(self):
+        self.bind()
+        flags = GL_MAP_READ_BIT #| GL_MAP_UNSYNCHRONIZED_BIT
+        return self.map(0, self._size, flags)
+        
+    def map_write(self):
+        self.bind()
+        flags = GL_MAP_WRITE_BIT | GL_MAP_UNSYNCHRONIZED_BIT \
+            | GL_MAP_INVALIDATE_BUFFER_BIT
+        return self.map(0, self._size, flags)
+    
+    def unmap(self):
+        self.bind()
+        result = glUnmapBuffer(self.TARGET)
+        assert result == GL_TRUE
+        self.unbind()
+        
+    @classmethod
+    def Data(cls, size):
+        glBufferData(cls.TARGET, size, None, cls.USAGE)
+        
+    @classmethod
+    def SubData(cls, size, offset, ptr):
+        glBufferSubData(cls.TARGET, size, offset, ptr)
+        
+    def reallocate(self):
+        assert self._size > 0
+        self.bind()
+        self.Data(self._size)
+        self.unbind()
+    
+################################################################################

          
A => liminal_legacy/liminal/engine/camera.py +578 -0
@@ 0,0 1,578 @@ 
+from __future__ import (print_function, division, absolute_import)
+
+import math
+import logging
+
+import sdl
+from glm import mat4, ortho2d, ortho3d, rotate, X_AXIS, Y_AXIS, Z_AXIS, vec3
+from glue import (lazydecls, defproperty, autoinit, expect_empty)
+
+from .actor import Actor
+from .interface import Named
+from .config import Config, StereoMode
+from .render import CameraAttribs, RNode, RJCMD_DRAW
+from .system import System
+from .hmd import HMDManager
+from .timing import TimeManager
+from .core import RenderManager
+from .input import Binding, InputSet, Mouse
+
+################################################################################
+
+log = logging.getLogger('liminal.camera')
+
+VEC_SCALE1 = vec3(1,1,1)
+
+################################################################################
+
+class ViewActor(Actor):
+    __slots__ = [
+        '_lens',
+        '_ortho_scale',
+        '_ortho_sensor',
+        '_near',
+        '_far',
+        '_perspective',
+        '_sensor_width',
+        '_sensor_height',
+        '_view_attribs',
+        '_stereo_view',
+        '_view_attrib_count',
+    ]
+    
+    def __init__(self, geom):
+        Actor.__init__(self, geom)
+        self._lens = math.radians(90.0)
+        self._ortho_scale = 1.0
+        self._ortho_sensor = False
+        self._near = 0.1
+        self._far = 100.0
+        self._sensor_width = 1.0
+        self._sensor_height = 1.0
+        self._perspective = True
+        self._stereo_view = Config().stereo_mode != StereoMode.OFF
+        self._view_attrib_count = 1
+        self._view_attribs = None
+
+    def set_sensor(self, size):
+        self.sensor_width = size.x
+        self.sensor_height = size.y
+        
+    @property
+    def stereo_view(self):
+        return self._stereo_view
+        
+    @property
+    def sensor_width(self):
+        return self._sensor_width
+    @sensor_width.setter
+    def sensor_width(self, value):
+        self._sensor_width = value
+    
+    @property
+    def sensor_height(self):
+        return self._sensor_height
+    @sensor_height.setter
+    def sensor_height(self, value):
+        self._sensor_height = value
+        
+    @property
+    def ortho_sensor(self):
+        return self._ortho_sensor
+    @ortho_sensor.setter
+    def ortho_sensor(self, value):
+        self._ortho_sensor = value
+        
+    @property
+    def aspect(self):
+        return self._sensor_width / self._sensor_height
+        
+    @property
+    def lens(self):
+        return self._lens
+    @lens.setter
+    def lens(self, value):
+        self._lens = value
+
+    # (tfov = tan(fov/2))
+    # tfovx2 = aspect * tfovy2
+    # tfovy2 = tfovx2 / aspect 
+        
+    @property
+    def fov(self):
+        tfovx2 = math.tan(self._lens*0.5) * self.aspect
+        return math.degrees(math.atan(tfovx2) * 2.0) 
+    @fov.setter
+    def fov(self, value):
+        tfovy2 = math.tan(math.radians(value)*0.5) / self.aspect
+        self._lens = math.atan(tfovy2) * 2.0
+    
+    @property
+    def ortho_scale(self):
+        return self._ortho_scale
+    @ortho_scale.setter
+    def ortho_scale(self, value):
+        self._ortho_scale = value
+    
+    @property
+    def near(self):
+        return self._near
+    @near.setter
+    def near(self, value):
+        self._near = value
+    
+    @property
+    def far(self):
+        return self._far
+    @far.setter
+    def far(self, value):
+        self._far = value
+        self.invalidate_uniforms()
+    
+    @property
+    def perspective(self):
+        return self._perspective
+    @perspective.setter
+    def perspective(self, value):
+        self._perspective = value
+    
+    def adjust_eye_orientation(self, tf):
+        return tf
+
+    def adjust_eye_transform(self, tf):
+        return tf
+    
+    def _get_transform_matrix(self):
+        p,s,o = self.get_position_scale_orientation()
+        return mat4.compose(p,VEC_SCALE1,o)
+            
+    @property
+    def eye_orientation(self):
+        return self.adjust_eye_orientation(self.orientation)
+    
+    @property
+    def eye_transform(self):
+        return self.adjust_eye_transform(self.transform)
+        
+    @property
+    def world_eye_transform(self):
+        raise AttributeError, "no longer valid"
+
+    @property
+    def world_eye_orientation(self):
+        raise AttributeError, "no longer valid"
+        
+    def look_at(self, eye, center, up=vec3(0,0,1)):
+        self.transform = mat4.look_at(eye, center, up)
+    
+    @classmethod
+    def make_ray(cls, mtx, pos):
+        v0 = mtx.mul_vec3(vec3(pos.x,pos.y,0))
+        v1 = mtx.mul_vec3(vec3(pos.x,pos.y,-1))
+        return v0, v1.sub(v0).normalize() 
+    
+    def get_ray(self, pos):
+        return self.make_ray(self.eye_transform, pos)
+    
+    def to_projection_eye_matrix(self, eye_id):
+        if self._perspective:
+            return mat4.perspective(
+                math.degrees(self.lens), self.aspect, self.near, self.far), None
+        else:
+            s = self._ortho_scale
+            sy = s*0.5 
+            sx = s*0.5
+            if self._ortho_sensor:
+                sx *= self._sensor_width
+                sy *= self._sensor_height
+            else:
+                sx *= self.aspect
+            return ortho3d(-sx, sx, -sy, sy, self.near, self.far), None
+    
+    def to_view_matrix(self):
+        return self.eye_transform.inverse()
+    
+    def update_uniforms(self):
+        Actor.update_uniforms(self)
+        for k, attrib in enumerate(self.view_attribs):
+            with attrib.map_write() as data:
+                self.write_camera_attribs(attrib, data, k)
+    
+    def adjust_proj_matrix(self, attrib, proj_mtx, view_mtx):
+        return proj_mtx
+    
+    def write_camera_attribs(self, attrib, data, eye_id):
+        if self._stereo_view and eye_id == 0:
+            eye_id = -1
+        
+        camera_mtx = self.eye_transform
+        
+        proj_mtx,eye_mtx = self.to_projection_eye_matrix(eye_id)
+        if eye_mtx:
+            camera_mtx = camera_mtx.mul_mat4(eye_mtx)
+        
+        view_mtx = camera_mtx.inverse()
+        proj_mtx = self.adjust_proj_matrix(attrib, proj_mtx, view_mtx)
+        
+        inv_proj_matrix = proj_mtx.inverse()        
+        viewproj_mtx = proj_mtx.mul_mat4(view_mtx)
+        
+        data.mtx_view = view_mtx._ptr[0]
+        data.mtx_camera = camera_mtx._ptr[0]
+        data.mtx_proj = proj_mtx._ptr[0]
+        data.mtx_viewproj = viewproj_mtx._ptr[0]
+        data.mtx_inv_proj = inv_proj_matrix._ptr[0]
+        data.near = self.near
+        data.far = self.far
+        data.time = TimeManager().duration
+        attrib.view_matrix = view_mtx
+        attrib.projection_matrix = proj_mtx
+        
+    @property
+    def view_attribs(self):
+        self.ensure_view_attribs()
+        return self._view_attribs
+    
+    def get_view_attrib(self, eye_id):
+        return self.view_attribs[eye_id > 0]        
+    
+    def ensure_view_attribs(self):
+        if not self._view_attribs:
+            if self._stereo_view:
+                attrib_count = 2
+            else:
+                attrib_count = self._view_attrib_count
+            view_attribs = []
+            self._view_attribs = view_attribs 
+            for i in range(attrib_count):
+                attrib = CameraAttribs()
+                attrib.layer_id = i
+                view_attribs.append(attrib)
+            self.update_uniforms()
+
+################################################################################
+
+class Camera(ViewActor):
+    __slots__ = [
+        '_framebuffer',
+        '_light_buffer',
+        '_clear_depth',
+        '_clear_color',
+        '_clear_stencil',
+    ]
+    
+    def __init__(self, geom):
+        ViewActor.__init__(self, geom)
+        self._framebuffer = None
+        self._light_buffer = None
+        self._clear_depth = True
+        self._clear_color = None
+        self._clear_stencil = False
+        
+    def process(self, batch, jobs):
+        RNode.process(self, batch, jobs)
+        self.invalidate_uniforms()
+        self.ensure_view_attribs()
+        
+        vp_indices = [batch.get_viewport_index(vp)
+            for vp in RenderManager().render_viewports]
+                
+        for job in list(jobs):
+            if job.key.command != RJCMD_DRAW: continue
+            for i in range(len(self._view_attribs)):
+                view_attrib = self.get_view_attrib(i)
+                if i > 0:
+                    job = batch.copy_job(job)
+                    jobs.append(job)
+                job.draw.ubo_camera = view_attrib
+                job.viewport_index = vp_indices[i]
+                job.key.viewport = job.viewport_index
+        
+    @property
+    def light_buffer(self):
+        return self._light_buffer
+    @light_buffer.setter
+    def light_buffer(self, value):
+        self._light_buffer = value
+        
+    @property
+    def framebuffer(self):
+        return self._framebuffer
+    @framebuffer.setter
+    def framebuffer(self, value):
+        self._framebuffer = value
+        
+    def write_camera_attribs(self, attrib, data, eye_id):
+        ViewActor.write_camera_attribs(self, attrib, data, eye_id)
+        
+        vp_matrix = tuple(data.mtx_viewproj.a[0:16])
+        
+        last_vp_matrix = attrib.last_vp_matrix
+        if last_vp_matrix is None:
+            last_vp_matrix = vp_matrix        
+        attrib.last_vp_matrix = vp_matrix
+         
+        data.mtx_last_vp.a = last_vp_matrix
+        
+    @property
+    def clear_depth(self):
+        return self._clear_depth
+    @clear_depth.setter
+    def clear_depth(self, value):
+        self._clear_depth = value
+    
+    @property
+    def clear_color(self):
+        return self._clear_color
+    @clear_color.setter
+    def clear_color(self, value):
+        self._clear_color = value
+        
+    @property
+    def clear_stencil(self):
+        return self._clear_stencil
+    @clear_stencil.setter
+    def clear_stencil(self, value):
+        self._clear_stencil = value
+
+################################################################################
+
+class ScreenCamera(Camera):
+    __slots__ = [
+    ]
+    
+    def __init__(self, geom):
+        Camera.__init__(self, geom)
+        self.use_screen_sensor()
+        self.fov = 90.0
+        
+    def update_uniforms(self):
+        Camera.update_uniforms(self)
+        if Config().stereo_mode == StereoMode.OCULUS_RIFT:
+            self.invalidate_uniforms() 
+        
+    def use_screen_sensor(self):
+        size = System().screensize
+        if Config().stereo_mode == StereoMode.OCULUS_RIFT:
+            size.x //= 2
+            
+        self.set_sensor(size) 
+    
+    def to_projection_eye_matrix(self, eye_id):
+        cfg = Config()
+        if self._perspective:
+            if cfg.stereo_mode == StereoMode.OCULUS_RIFT:
+                hmd = HMDManager()
+                fov = math.degrees(hmd.get_fovy())
+                
+                proj_mtx = mat4.perspective(
+                    fov, self.aspect, self.near, self.far)
+                
+                h = hmd.get_horizontal_center_offset()
+                
+                H = mat4.translation_fff(
+                    eye_id * -h, 0.0, 0.0)
+
+                dip = hmd.interpupillary_distance
+
+                V = mat4.translation_fff(
+                    eye_id * (dip/2.0), 0.0, 0.0)
+                
+                proj_mtx = H.mul_mat4(proj_mtx)
+            else:
+                proj_mtx = mat4.perspective(
+                    math.degrees(self.lens), self.aspect, self.near, self.far)
+                
+                H = mat4.translation_fff(
+                    eye_id * cfg.fpd, 0.0, 0.0)
+
+                V = mat4.translation_fff(
+                    eye_id * cfg.ipd, 0.0, 0.0)
+                
+                proj_mtx = H.mul_mat4(proj_mtx)
+            return proj_mtx, V
+        else:
+            s = self._ortho_scale
+            if cfg.stereo_mode == StereoMode.OCULUS_RIFT:
+                s *= 3.0
+            sy = s*0.5 
+            sx = s*0.5
+            if self._ortho_sensor:
+                sx *= self._sensor_width
+                sy *= self._sensor_height
+            else:
+                sx *= self.aspect
+            mtx = ortho2d(-sx, sx, -sy, sy)
+            if cfg.stereo_mode == StereoMode.OCULUS_RIFT:
+                hmd = HMDManager()
+                h = hmd.get_horizontal_center_offset()
+                
+                H = mat4.translation_fff(
+                    eye_id * -h, 0.0, 0.0)
+
+                mtx = H.mul_mat4(mtx)
+                
+            return mtx, None
+    
+    def adjust_eye_orientation(self, tf):
+        if self._perspective and Config().stereo_mode == StereoMode.OCULUS_RIFT:
+            mat_o = HMDManager().get_orientation().to_mat3()
+            return tf.mul_mat3(mat_o)
+        return tf
+
+    def adjust_eye_transform(self, tf):
+        if self._perspective and Config().stereo_mode == StereoMode.OCULUS_RIFT:
+            mat_o = HMDManager().get_orientation()
+            return tf.mul_mat4(mat_o).mul_mat4(mat4.translation_fff(0.0, 0.12, -0.08))
+        return tf
+
+################################################################################
+
+class FPSControls(InputSet):
+    move_left = Binding()
+    move_left.key(sdl.SDLK_a)
+    
+    move_right = Binding()
+    move_right.key(sdl.SDLK_d)
+    
+    move_forward = Binding()
+    move_forward.key(sdl.SDLK_w)
+    
+    move_backward = Binding()
+    move_backward.key(sdl.SDLK_s)
+    
+    move_up = Binding()
+    move_up.key(sdl.SDLK_r)
+    
+    move_down = Binding()
+    move_down.key(sdl.SDLK_f)
+    
+    roll_left = Binding()
+    roll_left.key(sdl.SDLK_q)
+    
+    roll_right = Binding()
+    roll_right.key(sdl.SDLK_e)
+    
+    run = Binding()
+    run.key(sdl.SDLK_LSHIFT)
+
+            
+################################################################################
+
+@lazydecls
+class DebugCamera(ScreenCamera):
+    speed_walk = defproperty(default = 5.0)
+    speed_run = defproperty(default = 30.0)
+    allow_move = defproperty(default = True)
+    lock_x = defproperty(default = False)
+    lock_y = defproperty(default = False)
+    lock_z = defproperty(default = False)
+    lock_up = defproperty(default = True)
+    mouse_strafe = defproperty(default = False)
+    
+    def __init__(self, geom, **kwargs):
+        ScreenCamera.__init__(self, geom)
+        self.name = 'debug-camera'
+        kwargs = autoinit(DebugCamera, self, **kwargs)
+        self.controls = FPSControls()
+        self.controls.event_frame.addweak(self.on_controls)
+        self.mouse = Mouse(self.controls)
+        self.transform = mat4.look_at(
+            vec3(0,-1,0), vec3(0,1,0), vec3(0,0,1))
+        if kwargs.pop('disable_on_grab_release', True):
+            self.controls.disable_on_grab_release()
+        expect_empty(kwargs)
+        self.controls.push()
+    
+    def on_controls(self):
+        rift_mode = Config().stereo_mode == StereoMode.OCULUS_RIFT
+        
+        mtx = self.transform 
+        mtx_orient = self.eye_orientation
+        
+        controls = self.controls
+        
+        if controls.run.active:
+            SPEED = self.speed_run
+        else:
+            SPEED = self.speed_walk
+            
+        SPEED *= TimeManager().timedelta
+            
+        mouse_strafe = self.mouse_strafe
+        
+        moved = 0
+        
+        mouse = self.mouse
+            
+        if not mouse_strafe:
+            x,y = mouse.smooth_relative
+            if x:
+                mtx = rotate(mtx, -x*0.1, Y_AXIS)
+                moved += 1
+            if y and not rift_mode:
+                mtx = rotate(mtx, -y*0.1, X_AXIS)
+                moved += 1
+            
+        if self.allow_move:
+            if mouse_strafe:
+                if not self.lock_x:
+                    mtx.col3_vec4(mtx.col3().sub_vec4(
+                        mtx_orient.col0().to_vec4().mul_f(-x*0.001)))
+                    moved += 1
+                if not self.lock_y:
+                    mtx.col3_vec4(mtx.col3().sub_vec4(
+                        mtx_orient.col1().to_vec4().mul_f(y*0.001)))
+                    moved += 1
+                
+            if not self.lock_up:   
+                if controls.roll_left.active: #Q
+                    mtx = rotate(mtx, 1.0, Z_AXIS)
+                    moved += 1
+                if controls.roll_right.active: #E
+                    mtx = rotate(mtx, -1.0, Z_AXIS)
+                    moved += 1
+                
+            if not self.lock_x:
+                if controls.move_left.active: #A
+                    mtx.col3_vec4(mtx.col3().sub_vec4(
+                        mtx_orient.col0().to_vec4().mul_f(SPEED)))
+                    moved += 1
+                if controls.move_right.active: #D
+                    mtx.col3_vec4(mtx.col3().add_vec4(
+                        mtx_orient.col0().to_vec4().mul_f(SPEED)))
+                    moved += 1
+            if not self.lock_y:
+                if controls.move_up.active: #R
+                    mtx.col3_vec4(mtx.col3().add_vec4(
+                        mtx_orient.col1().to_vec4().mul_f(SPEED)))
+                    moved += 1
+                if controls.move_down.active: #F
+                    mtx.col3_vec4(mtx.col3().sub_vec4(
+                        mtx_orient.col1().to_vec4().mul_f(SPEED)))
+                    moved += 1
+            if not self.lock_z:
+                if controls.move_forward.active: #W
+                    mtx.col3_vec4(mtx.col3().sub_vec4(
+                        mtx_orient.col2().to_vec4().mul_f(SPEED)))
+                    moved += 1
+                if controls.move_backward.active: #S
+                    mtx.col3_vec4(mtx.col3().add_vec4(
+                        mtx_orient.col2().to_vec4().mul_f(SPEED)))
+                    moved += 1
+                    
+        if not moved:
+            return
+                    
+        if self.lock_up:
+            pos = mtx.get_translation()
+            rot = mtx.to_mat3()
+            right = mtx.col0().xyz
+            right.z = 0.0
+            rot = rot.align_x_vec3(right, 1.0)
+            mtx = mat4.compose(pos, vec3(1,1,1), rot)
+            
+        self.transform = mtx
+
+################################################################################
+

          
A => liminal_legacy/liminal/engine/config.py +392 -0
@@ 0,0 1,392 @@ 
+from __future__ import (print_function, division, absolute_import)
+
+import os
+import sys
+import json
+import logging
+
+from argparse import ArgumentParser
+from types import NoneType
+
+import gl 
+
+from glue import lazydecls, callset, defproperty, Singleton, Undefined, autoinit
+from ..utils import loglevel_add_arguments, loglevel_apply_args, clamp
+
+################################################################################
+
+log = logging.getLogger('liminal.config')
+
+IS_DARWIN = sys.platform == 'darwin'
+IS_WIN32 = sys.platform == 'win32'
+
+################################################################################
+
+class StereoMode:
+    OFF = 0
+    SIDE_BY_SIDE = 1
+    OCULUS_RIFT = 2
+
+################################################################################
+
+class VideoQuality:
+    """
+    Video Quality regulates which non-essential effects are turned on at which
+    resolutions. Lower quality means less effect at lower sampling rates,
+    higher quality means more effects at higher sampling rates.
+    
+    Here's a table of which effects are affected at each quality level, at
+    which quality level, with a special column for Oculus Rift mode.
+    
+    - = disabled
+    * = as dictated by quality level
+    
+                             | OVR | --- | --  |  -  |  =  |  +  | ++  | +++ |
+    -------------------------+-----+-----+-----+-----+-----+-----+-----+-----+
+    SSAO                     |  *  |  -  |  -  |  -  | 8/2 | 8/1 |16/1 |16/1 |
+    -------------------------+-----+-----+-----+-----+-----+-----+-----+-----+
+    Bloom                    |  *  |  -  | /8  | /4  | /2  | /1  | /1  | /1  |
+    -------------------------+-----+-----+-----+-----+-----+-----+-----+-----+
+    Depth of Field           |  -  |  -  |  -  |  -  |  8  |  8  | 16  | 16  |
+    -------------------------+-----+-----+-----+-----+-----+-----+-----+-----+
+    Motion Blur              |  -  |  -  |  -  |  -  |  8  |  8  | 16  | 16  |
+    -------------------------+-----+-----+-----+-----+-----+-----+-----+-----+
+    EVSM Shadow Maps         |  *  | ^9  | ^9  | ^9  | ^9  |^10x2|^10x2|^10x4|
+    -------------------------+-----+-----+-----+-----+-----+-----+-----+-----+
+    FXAA Anti-Aliasing       |  -  |  -  |  -  |  -  |  -  | 12  | 20  | 29  |
+    -------------------------+-----+-----+-----+-----+-----+-----+-----+-----+
+    Shadow Map Cascades      |  *  |  1  |  1  |  2+ |  3+ |  3+ |  4+ |  5+ |
+    -------------------------+-----+-----+-----+-----+-----+-----+-----+-----+
+    Normal Mapping           |  *  |  -  |  -  |  -  |  -  |  Y  |  Y  |  Y  |
+    -------------------------+-----+-----+-----+-----+-----+-----+-----+-----+
+    """
+    
+    # overkill - best suited to take beauty shots, not recommended for playing
+    HIGHEST = 6  
+    
+    # setting designed to deliver good quality for 
+    # modern graphics card
+    HIGHER = 5 
+    
+    # more beauty, less performance: this is the default
+    HIGH = 4 
+    
+    # trade-off between looks and performance
+    MEDIUM = 3 
+    
+    # more performance, less beauty
+    LOW = 2 
+    
+    # lowest setting with only essential effects enabled
+    LOWER = 1 
+    
+    # total fallback - all effects off, least effort necessary to 
+    # display a consistent picture.
+    LOWEST = 0
+    
+    DEFAULT = HIGH
+
+################################################################################
+
+@lazydecls
+class Config(object):
+    __metaclass__ = Singleton
+    
+    SETTINGS_VARS = {
+        'fullscreen',
+        'videoquality',
+        'width',
+        'height'
+    }
+    
+    videomode = defproperty(default = None, types = (tuple, str, NoneType))
+    glversion = defproperty(default = None, types = (tuple, NoneType))
+    bail_on_error = defproperty(default = True, types = bool)
+    width = defproperty(default = None, types = (int, NoneType))
+    height = defproperty(default = None, types = (int, NoneType))
+    fullscreen = defproperty(default = False, types = bool)
+    vsync = defproperty(default = True, types = bool)
+    msaa = defproperty(default = None, types = (int, NoneType))
+    use_sound = defproperty(default = True, types = bool)
+    xpos = defproperty(default = -1, types = int)
+    ypos = defproperty(default = -1, types = int)
+    enable_profiler = defproperty(default = False, types = bool)
+    profiler_path = defproperty(default = None, types = (str, NoneType))
+    videoquality = defproperty(default = VideoQuality.DEFAULT, types = int)
+    
+    disable_logic = defproperty(default = False, types = bool)
+    stereo_mode = defproperty(default = StereoMode.OFF, types = int)
+    enable_ffpemu = defproperty(default = True, types = bool)
+    print_fps = defproperty(default = False, types = bool)
+    
+    """
+    how to turn sequence into gif:
+    convert -delay 3 -resize 320x180 -loop 0 *.png ../test_cube.gif
+    
+    into video:
+    ffmpeg -start_number 0 -f image2 -i '%04d.png' -vcodec libx264 -b 800k ../video.avi
+    """    
+    dump_image_path = defproperty(default = None, types = str)
+    
+    # dump renderjobs to console
+    print_renderjobs = defproperty(default = False, types = bool)
+    # permutate framebuffer formats and print results
+    test_fb_formats = defproperty(default = False, types = bool)
+    # open debug GL context
+    debug_context = defproperty(default = False, types = bool)
+    
+    # for stereo rendering:
+    fpd = defproperty(default = 0.0, types = float) # screen width 
+    ipd = defproperty(default = 0.0, types = float) # interpupillary distance
+    
+    base_name =  defproperty(default = 'liminal', types = str)
+    company_name =  defproperty(default = 'duangle', types = str)
+    base_path = defproperty(default = None, types = (str, NoneType))
+    userdir_path = defproperty(default = None, types = (str, NoneType))
+    
+    headless = defproperty(default = False, types = bool)
+    editable = defproperty(default = False, types = bool)
+    rebuild_assets = defproperty(default = False, types = bool)
+    webui_port = defproperty(default = 5000, types = int)
+
+    def __init__(self):
+        self._args_parsed = False
+        autoinit(Config, self)
+    
+    def parse_argv(self):
+        if self._args_parsed:
+            return
+        self._args_parsed = True
+        parser = ArgumentParser()
+        self.add_arguments(parser)
+        args = parser.parse_args()
+        self.apply_args(args)
+    
+    @property
+    def settings_dict(self):
+        settings = {}        
+        for varname in self.SETTINGS_VARS:
+            value = getattr(self, varname)
+            if value == getattr(Config, varname).default:
+                continue
+            settings[varname] = value
+        return settings
+    
+    @settings_dict.setter
+    def settings_dict(self, settings):
+        for varname in self.SETTINGS_VARS:
+            value = settings.get(varname, Undefined)
+            if value is Undefined:
+                continue
+            setattr(self, varname, value)
+    
+    @property
+    def settings_path(self):
+        return os.path.join(self.userdir_path, 'settings.json')
+    
+    def add_arguments(self, parser):
+        loglevel_add_arguments(parser)
+        
+        parser.add_argument('--videomode', dest='videomode', metavar='MODE',
+            help='Videomode of fullscreen. Use "list" for a list of modes.')
+        parser.add_argument('--novsync', action='store_true', dest="novsync",
+            help="disable vertical frequency synchronization")
+        parser.add_argument('--nosound', action='store_true', dest="nosound",
+            help="disable OpenAL audio engine")
+        parser.add_argument('--profiler', dest='profiler', metavar='FILEPATH',
+            help="Run main loop in profiler and save results to FILEPATH")
+        parser.add_argument('--fullscreen', action='store_true', dest="fullscreen",
+            help="enable fullscreen")
+        parser.add_argument('--gl-ignore-errors', action='store_true', dest="gl_ignore_errors",
+            help="ignore GL errors")
+        parser.add_argument('--ignore-errors', action='store_true', dest="ignore_errors",
+            help="Do not bail on errors")
+        parser.add_argument('--xpos', metavar='XPOS', dest="xpos", default=-1,
+            type=int, 
+            help="X position of window")
+        parser.add_argument('--ypos', metavar='YPOS', dest="ypos", default=-1,
+            type=int, 
+            help="Y position of window")
+        parser.add_argument('--width', metavar='WIDTH', dest="width", default=0,
+            type=int, 
+            help="Width of window")
+        parser.add_argument('--height', metavar='HEIGHT', dest="height", default=0,
+            type=int, 
+            help="Height of window")
+        parser.add_argument('--video-quality', metavar='QUALITY', dest="quality", 
+            default=VideoQuality.DEFAULT, type=int, 
+            help="Quality of shaders and rendering [0-6] (0 = fastest, 6 = prettiest)")
+        parser.add_argument('--glarb', metavar='GLARB', dest="glarb", default=None,
+            choices = ['3.2','3.3','4.0','4.1','4.2','4.3','4.4','0.0'], type=str, 
+            help="use GL version (3.0 = shadermodel 1.30, 3.2 = shadermodel 1.50)")
+        parser.add_argument('--msaa', metavar='LEVEL', dest="msaa", default=0,
+            choices = range(33), type=int,
+            help="use anti-aliasing level (0..32)")
+
+        parser.add_argument('--nologic', action='store_true', dest="nologic",
+            help="disable handling of logic controllers")
+        parser.add_argument('--noffpemu', action='store_true', dest="noffpemu",
+            help="disable fixed function pipeline emulation interface")
+        parser.add_argument('--3d', metavar='MODE', dest="stereo_mode",
+            choices = ['sbs', 'or'], default = None, type = str,
+            help="Render in stereoscopic 3D (sbs: Side-By-Side, or: Oculus Rift)")
+        parser.add_argument('--dump-image-sequence', metavar='NAME', dest="dump_image_path",
+            default = None, type = str,
+            help="dump each frame to image to subfolder")
+        parser.add_argument('--printfps', action='store_true', dest="printfps",
+            help="Print FPS statistics in intervals.")
+        parser.add_argument('--debug-wireframe', action='store_true', dest="wireframe",
+            help="render all materials as wireframe")
+        parser.add_argument('--debug-renderjobs', action='store_true', dest="debug_renderjobs",
+            help="dump renderjobs of current frame to console")
+        parser.add_argument('--debug-context', action='store_true', dest="debug_context",
+            help="Open debug GL context, prints additional warnings to the console")
+        parser.add_argument('--debug-fb-formats', action='store_true', dest="debug_fb_formats",
+            help="permutate list of framebuffer formats and dump to console")
+        parser.add_argument('-p', '--project', dest="project",
+            help="Path to project folder")
+        parser.add_argument('--webui-port', dest="webui_port", type=int,
+            help="Port on which to run web UI", default=5000)
+        parser.add_argument('--headless', action='store_true', dest="headless",
+                help="run web UI only")
+        parser.add_argument('--rebuild-assets', action='store_true', 
+            dest="rebuild_assets", help="rebuild editor assets on change")    
+        parser.add_argument('--edit', action='store_true', default = False,
+            dest="editable", help="enable editing UI")    
+
+
+    def apply_args(self, args):
+        loglevel_apply_args(args)
+        
+        if self.base_path is None:
+            print("ERROR: Config().base_path is not set")
+            raise SystemExit, 255
+        
+        if not os.path.isdir(self.base_path):
+            print("ERROR: {} is not a directory.".format(self.base_path))
+            raise SystemExit, 255
+        
+        if IS_WIN32:
+            self.userdir_path = os.path.normpath(
+                '{APPDATA}/{companyname}/{basename}'.format(
+                    companyname = self.company_name, 
+                    basename = self.base_name, **os.environ))
+        else:
+            CONFIG_HOME = os.path.expanduser('~/.config')
+            if 'XDG_CONFIG_HOME' in os.environ:
+                CONFIG_HOME = os.environ['XDG_CONFIG_HOME']
+            self.userdir_path = os.path.normpath(
+                os.path.join(CONFIG_HOME, self.company_name, self.base_name))
+        
+        if not os.path.isdir(self.userdir_path):
+            os.makedirs(self.userdir_path)
+            
+        if not self.settings_exist():
+            self.save_settings()
+        self.load_settings()
+
+        if args.videomode == 'list':
+            self.videomode = 'list'
+        elif args.videomode:
+            self.videomode = tuple([int(v) for v in args.videomode.split('.')])
+        
+        if args.glarb:
+            self.glversion = tuple([int(v) for v in args.glarb.split('.')])
+
+        if args.gl_ignore_errors:
+            gl.internal.RAISE_ON_ERROR = False
+
+        if args.ignore_errors:
+            self.bail_on_error = False
+        
+        self.videoquality = clamp(args.quality,
+            VideoQuality.LOWEST, VideoQuality.HIGHEST)
+        
+        if args.width:
+            self.width = args.width
+        if args.height:
+            self.height = args.height
+        if args.fullscreen:
+            self.fullscreen = True
+        if args.novsync:
+            self.vsync = False
+        if args.msaa:
+            self.msaa = int(args.msaa)
+        if args.nosound:
+            self.use_sound = False
+        if args.xpos:
+            self.xpos = args.xpos
+        if args.ypos:
+            self.ypos = args.ypos
+        if args.profiler:
+            self.enable_profiler = True
+            self.profiler_path = args.profiler
+            
+        if args.nologic:
+            self.disable_logic = True
+            
+        if args.stereo_mode:
+            self.stereo_mode = {
+                'sbs' : StereoMode.SIDE_BY_SIDE,
+                'or' : StereoMode.OCULUS_RIFT
+            }[args.stereo_mode]
+            if self.stereo_mode == StereoMode.SIDE_BY_SIDE:
+                SCREEN_WIDTH = 0.505
+                EYE_WIDTH = 0.07 #0.067
+                # width of my screen: 0.505 (50.5cm)
+                # eye width 0.07 (70 mm)
+                self.ipd = EYE_WIDTH
+                self.fpd = self.ipd / SCREEN_WIDTH
+            
+        if args.dump_image_path is not None:
+            self.dump_image_path = args.dump_image_path
+            
+        if args.noffpemu:
+            self.enable_ffpemu = False
+            
+        if args.printfps:
+            self.print_fps = True
+            
+        if args.wireframe:
+            from . import material 
+            material.GLMaterial.DEFAULT_WIREFRAME = True
+        if args.debug_renderjobs:
+            self.print_renderjobs = True
+
+        if args.debug_context:
+            self.debug_context = True
+
+        if args.debug_fb_formats:
+            self.test_fb_formats = True
+
+        if args.editable:
+            self.editable = True
+        
+        if args.headless:    
+            self.headless = args.headless
+            self.editable = True
+            
+        if args.rebuild_assets:
+            self.rebuild_assets = args.rebuild_assets
+            
+        if args.webui_port:
+            self.webui_port = args.webui_port
+            
+            
+    def settings_exist(self):
+        return os.path.isfile(self.settings_path)
+    
+    def load_settings(self):
+        if not self.settings_exist():
+            return
+        log.info("Loading config from {}".format(self.settings_path))
+        with open(self.settings_path, 'r') as f:
+            obj = json.load(f) 
+        self.settings_dict = obj
+    
+    def save_settings(self):
+        with open(self.settings_path, 'w') as f:
+            json.dump(self.settings_dict, f)
+        log.info("Saved config to {}".format(self.settings_path))
+        
+    
  No newline at end of file

          
A => liminal_legacy/liminal/engine/core.py +446 -0
@@ 0,0 1,446 @@ 
+from __future__ import (print_function, division, absolute_import)
+
+import logging
+import time
+import os
+import sys
+
+from glm import ivec4, vec4
+from glue import Singleton, callset
+from stbimage import stbi_write_png, stbi_flip_y
+
+from gl import glEnable, GL_TEXTURE_CUBE_MAP_SEAMLESS
+
+from .render import (Viewport, NullFramebuffer, RenderBatch,
+    RViewportNode, RClearNode)
+from .system import System
+from .interface import Named
+from .config import Config, StereoMode, VideoQuality
+from .framebuffer import Framebuffer  
+from .hmd import HMDManager
+from .profiler import FrameProfiler
+from .timing import TimeManager
+from liminal.engine.render import RCustomNode
+
+IS_DARWIN = sys.platform == 'darwin'
+
+################################################################################
+
+log = logging.getLogger('liminal.core')
+
+################################################################################
+
+class FXAAMode:
+    Off = 0
+    #GreenAsLuma = 1
+    AlphaAsLuma = 2
+
+################################################################################
+
+class RenderManager(Named):
+    __metaclass__ = Singleton
+    
+    __slots__ = [
+        '_stereo_depth',
+        '_stereo_mode',
+        '_framebuffer',
+        '_viewport',
+        '_framebuffer_viewport',
+        '_render_viewports',
+        '_batch',
+        '_ppfx',
+        '_show_profile',
+        '_fxaa_mode',
+        '_nodes',
+        '_debug_textures',
+        '_clear_node',
+        '_invalid_uniforms',
+        
+        'start_time',
+        'last_timestamp',
+        'frames_rendered',
+        'frame_rate',
+        
+        'event_frame_rendered',
+        'event_gather',
+    ]
+
+    FPS_MEASURE_TIME = 1
+
+    def __init__(self):
+        Named.__init__(self)
+        self.name = 'render_tree'
+        self.frame_rate = 60.0
+        self._viewport = None
+        self._render_viewports = ()
+        self._batch = None
+        self._stereo_depth = 1.0 
+        self._clear_node = None
+        self._ppfx = None
+        if Config().videoquality >= VideoQuality.HIGH:
+            self._fxaa_mode = FXAAMode.AlphaAsLuma
+        else:
+            self._fxaa_mode = FXAAMode.Off
+        self._show_profile = False
+        self._debug_textures = []
+        self._framebuffer_viewport = None
+        self._invalid_uniforms = set()
+
+        self.frames_rendered = 0
+        self.start_time = time.time()
+        self.last_timestamp = self.start_time
+        self.event_frame_rendered = callset()
+        self.event_gather = callset()
+        
+        System.event_resized.add(self.on_resized)
+        System.event_draw.add(self.render)
+        
+    def init(self):
+        self._batch = RenderBatch()
+        self._batch.name = 'render'
+        self.init_viewport()
+        self.setup_nodes()
+        
+    @property
+    def batch_profile(self):
+        return self._batch.profile
+    
+    @property
+    def batch(self):
+        return self._batch
+        
+    @property
+    def fxaa_mode(self):
+        return self._fxaa_mode
+    @fxaa_mode.setter
+    def fxaa_mode(self, value):
+        self._fxaa_mode = value
+        
+    @property
+    def show_profile(self):
+        return self._show_profile
+    @show_profile.setter
+    def show_profile(self, value):
+        self._show_profile = value
+        
+    @property
+    def stereo_depth(self):
+        return self._stereo_depth
+    @stereo_depth.setter
+    def stereo_depth(self, value):
+        self._stereo_depth = value
+        
+    @property
+    def render_viewports(self):
+        return self._render_viewports
+        
+    @property
+    def viewport(self):
+        return self._viewport
+        
+    @property
+    def ppfx(self):
+        return self._ppfx
+    
+    @property
+    def framebuffer(self):
+        return self._framebuffer
+    
+    @property
+    def clear_node(self):
+        return self._clear_node
+    
+    @property
+    def framebuffer_viewport(self):
+        return self._framebuffer_viewport
+    
+    def set_root_ppfx(self, ppfx):
+        if ppfx == self._ppfx:
+            return
+        self._ppfx = ppfx
+        self._ppfx.add_framebuffer_textures(self._framebuffer)
+        
+    def setup_nodes(self):
+        batch = self.batch
+        
+        node_clear = RClearNode()
+        node_clear.name = 'root-clear'
+        node_clear.color = vec4(0,0,1,1)
+        self._clear_node = node_clear
+        
+        node_viewport = RViewportNode(self._viewport)
+        node_viewport.name = 'root'
+        self._framebuffer_viewport = node_viewport 
+        
+        batch << self._framebuffer << node_viewport << node_clear
+
+        # bounce ppfx to final framebuffer        
+        node_fb_null = NullFramebuffer        
+        
+        batch << node_fb_null
+        
+        for vp in self._render_viewports:
+            node_viewport = RViewportNode(vp)
+            node_viewport.name = 'null-{}'.format(vp.name)
+            node_fb_null << node_viewport << self._ppfx
+        
+            
+    def _add_oculus_rift_ppfx(self):
+        from .ppfx import PPFX
+        
+        #self._framebuffer = Framebuffer.from_system(scale=1.5, use_depth=True)
+        self._framebuffer = Framebuffer.from_system(use_depth=True)
+        self._framebuffer.name = 'root'
+        
+        ppfx = PPFX('ppfx/oculus_rift')
+        ppfx.name = 'root-oculus-rift'
+        self.set_root_ppfx(ppfx)
+        
+        hmd = HMDManager()
+        resolution = hmd.resolution
+        
+        winw,winh = System().screensize.to_tuple()
+        
+        x,y,w,h = 0,0,resolution.x*0.5,resolution.y
+        x /= winw
+        y /= winh
+        w /= winw
+        h /= winh
+        
+        aspect = resolution.x * 0.5 / resolution.y
+        
+        xcenteroffset = hmd.get_horizontal_center_offset()
+        
+        scale = hmd.get_distortion_scale()
+        scale_factor = 1.0 / scale
+        
+        ppfx.set_uniform_values(
+            LensCenter = (x + (w + xcenteroffset*0.5)*0.5, y + h*0.5),
+            ScreenCenter = (x + w*0.5, y + h*0.5),
+            Scale = ((w/2) * scale_factor, (h/2) * scale_factor * aspect),
+            ScaleIn = ((2/w), (2/h) / aspect),
+            HmdWarpParam = hmd.distortion_k,
+            ChromAbParam = hmd.chroma_ab_correction)  
+
+        txcolor = ppfx.textures[0]
+        txcolor.bilinear = True
+        #self.debug_color_texture(txcolor)
+        #txcolor.anisotropic = True
+        
+    def _add_default_ppfx(self):
+        from .ppfx import PPFX
+        
+        if self._fxaa_mode == FXAAMode.AlphaAsLuma:
+            self._framebuffer = Framebuffer.from_system(
+                use_depth=True, use_alpha = True)
+            ppfx = PPFX('ppfx/fxaa3_11')
+        else:
+            self._framebuffer = Framebuffer.from_system(
+                use_depth=True)
+            ppfx = PPFX('ppfx/blit')
+        self._framebuffer.name = 'root'
+            
+        ppfx.name = 'root'
+        self.set_root_ppfx(ppfx)
+
+    def init_viewport(self):
+        self._viewport = Viewport()
+        
+        if Config().stereo_mode == StereoMode.OCULUS_RIFT:
+            self._add_oculus_rift_ppfx()
+        else:
+            self._add_default_ppfx()
+        
+        stereo_mode = Config().stereo_mode
+        if stereo_mode != StereoMode.OFF:
+            x,y,w,h = self._viewport.rect.to_tuple()
+            w2 = w*0.5            
+            
+            vpL = Viewport()
+            vpL.rect = vec4(x,y,w2,h)
+            vpL.eye_id = -1
+            
+            vpR = Viewport()
+            vpR.rect = vec4(x+w2,y,w2,h)
+            vpR.eye_id = 1
+            
+            self._render_viewports = (vpL, vpR)
+        else:
+            self._render_viewports = (self._viewport,)
+
+    def on_resized(self, size):
+        # update engines root viewport
+        pass
+
+    def write_screen_capture(self, filepath):
+        log.info("Writing {}...".format(filepath))
+        system = System()
+        w,h = system.screensize.to_tuple()
+        w -= w % 4
+        h -= h % 4
+        data = Framebuffer.read_screen_pixels(ivec4(0,0,w,h))
+        comp = 3 
+        stbi_flip_y(w, h, comp, data)
+        stbi_write_png(filepath, w, h, comp, data)
+
+    def make_screenshot(self):
+        basepath = Config().base_path
+        i = 0     
+        basename = time.strftime('screenshot_%Y-%m-%d_%H-%M_')
+        while i < 9999:
+            i += 1
+            filepath = os.path.join(basepath, basename + '{:04d}.png'.format(i))
+            if not os.path.exists(filepath):
+                break
+        self.write_screen_capture(filepath)
+
+    def make_batchgraphviz(self):
+        basepath = Config().base_path
+        i = 0     
+        basename = time.strftime('batchgraph_%Y-%m-%d_%H-%M_')
+        while i < 9999:
+            i += 1
+            filepath = os.path.join(basepath, basename + '{:04d}.png'.format(i))
+            if not os.path.exists(filepath):
+                break
+        self.batch.dump_graphviz(filepath)
+        
+    def invalidate_framebuffer_order(self):
+        self._render_ctx.batch.invalidate_framebuffer_order()
+
+    def frame_rendered(self):
+        self.frames_rendered += 1
+        timestamp = time.time()
+        if (timestamp - self.last_timestamp) > self.FPS_MEASURE_TIME:
+            fps = self.frames_rendered / self.FPS_MEASURE_TIME
+            self.last_timestamp = timestamp
+            self.frames_rendered = 0
+            self.frame_rate = fps
+            TimeManager().measured_framerate = fps
+            if Config().print_fps:
+                log.info("%.1f FPS" % (fps,))
+                #FrameProfiler().print_profile()
+        self.event_frame_rendered()
+
+    def debug_depth_texture(self, tex):
+        from .ppfx import PPFX        
+        ppfx = PPFX('ppfx_debug_depth')
+        ppfx.name = 'debug-ppfx-' + tex.name
+        ppfx.add_shader_texture('fb_color0', tex)
+        self.add_debug_ppfx(ppfx)
+
+    def debug_layer_depth_texture(self, tex):
+        from .ppfx import PPFX        
+        ppfx = PPFX('ppfx/debug_layer_depth')
+        ppfx.name = 'debug-ppfx-' + tex.name
+        ppfx.add_shader_texture('fb_color0', tex)
+        ppfx.set_uniform_values(
+            layers = tex.layers
+        )
+        self.add_debug_ppfx(ppfx)
+
+    def debug_layer_color_texture(self, tex):
+        from .ppfx import PPFX        
+        ppfx = PPFX('ppfx/debug_layer_color')
+        ppfx.name = 'debug-ppfx-' + tex.name
+        ppfx.add_shader_texture('fb_color0', tex)
+        ppfx.set_uniform_values(
+            layers = tex.layers
+        )
+        self.add_debug_ppfx(ppfx)
+
+    def debug_normal_texture(self, tex):
+        from .ppfx import PPFX        
+        ppfx = PPFX('ppfx/debug_normal')
+        ppfx.name = 'debug-ppfx-' + tex.name
+        ppfx.add_shader_texture('fb_color0', tex)
+        self.add_debug_ppfx(ppfx)
+
+    def debug_velocity_texture(self, tex):
+        from .ppfx import PPFX        
+        ppfx = PPFX('ppfx/debug_velocity')
+        ppfx.name = 'debug-ppfx-' + tex.name
+        ppfx.add_shader_texture('fb_color0', tex)
+        self.add_debug_ppfx(ppfx)
+
+    def debug_color_texture(self, tex, **kargs):
+        from .ppfx import PPFX        
+        ppfx = PPFX('ppfx/debug_color')
+        ppfx.name = 'debug-ppfx-' + tex.name
+        ppfx.add_shader_texture('fb_color0', tex)
+        self.add_debug_ppfx(ppfx, **kargs)
+
+    def debug_ycocg_texture(self, tex):
+        from .ppfx import PPFX        
+        ppfx = PPFX('ppfx/debug_ycocg')
+        ppfx.name = 'debug-ppfx-' + tex.name
+        ppfx.add_shader_texture('fb_color0', tex)
+        ppfx.uniforms.fb_size.i[0:2] = tex.size
+        self.add_debug_ppfx(ppfx)
+        
+    def debug_framebuffer(self, fb):
+        for tex in fb.color_attachments:
+            self.debug_color_texture(tex)
+        if fb.depth_attachment:
+            self.debug_depth_texture(fb.depth_attachment)
+        
+    def add_debug_ppfx(self, ppfx, rect=None):
+        TILES_X = 4
+        TILE_SIZE = 1.0 / TILES_X
+        
+        x = (len(self._debug_textures) % TILES_X) * TILE_SIZE
+        y = (len(self._debug_textures) // TILES_X) * TILE_SIZE
+        
+        vp = Viewport()
+        vp.name = 'debug-viewport'
+        if rect:
+            vp.rect = rect
+        else:
+            vp.rect = vec4(x,y,TILE_SIZE,TILE_SIZE)
+        
+        self.framebuffer << RViewportNode(vp) << ppfx
+        
+        #self._debug_textures.append((vp,ppfx))
+        
+    def dump_frame(self):
+        self._batch.dump()
+        
+    def render_debug_textures(self, ctx):
+        if not self._debug_textures:
+            return
+        
+        for vp,ppfx in self._debug_textures:
+            ppfx_ctx = RenderContext(ctx)
+            rt_ppfx = ppfx_ctx.rt_root
+            rt_ppfx.viewport = vp
+            rt_ppfx.scenepass = 1<<47
+            ppfx.render(ppfx_ctx)
+        
+    def invalidate_uniforms(self, actor):
+        assert actor is not None
+        self._invalid_uniforms.add(actor)
+        
+    def update_uniforms(self):
+        invalid_uniforms = self._invalid_uniforms
+        self._invalid_uniforms = set()        
+        for actor in invalid_uniforms:
+            actor.update_uniforms()
+
+    def render(self):
+        fp = FrameProfiler()
+        
+        with fp.section('update_uniforms'):
+            self.update_uniforms()
+        
+        tm = TimeManager()
+        with self._batch.global_attribs.map_write() as data:
+            data.time = tm.duration 
+        
+        self._batch.execute()
+        
+        if self._show_profile:
+            fp.render_profile()
+        
+        self.frame_rendered()
+
+################################################################################

          
A => liminal_legacy/liminal/engine/deferred.py +1423 -0
@@ 0,0 1,1423 @@ 
+#encoding: utf-8
+from __future__ import (print_function, division, absolute_import)
+
+import math
+
+from glue import callproxy, Singleton
+
+from gl import (glFenceSync, glClientWaitSync, glDeleteSync,
+    GL_SYNC_GPU_COMMANDS_COMPLETE, GL_CONDITION_SATISFIED,
+    GL_ALREADY_SIGNALED)
+from glm import vec2, vec3, vec4
+
+from .interface import Named, RNode
+from .texture import TextureWrap
+from .framebuffer import Framebuffer, FramebufferTexture2D
+from .shader import Program
+from .config import Config, StereoMode
+from .render import render_ffi, RClearNode
+from ..utils import clamp, Accum, f16_to_f32
+from .timing import TimeManager
+from .core import RenderManager
+from .config import VideoQuality
+from .pixelbuffer import PixelPackBuffer
+from .ppfx import PPFX, blur_pass_shader, PPFXResources
+from .material import Material
+from .actor import Actor
+from .camera import Camera
+
+################################################################################
+
+def Skybox():
+    mesh = PPFXResources().mesh
+    material = Material.from_files('g/skybox')
+    material.renderpass = 100
+    material.depth_test = True
+    material.depth_mask = False
+    
+    material << mesh
+    return material
+
+################################################################################
+
+class GRenderManager(RNode):
+    __metaclass__ = Singleton
+    
+    __slots__ = [
+        '_grading0',
+        '_grading1',
+        '_use_fog',
+        '_grading_mix',
+        '_framebuffer',
+        '_skybox',
+        '_scene_framebuffer',
+        '_use_motion_blur',
+        '_use_ssao',
+        '_ppfx_tonemap',
+        '_ppfx_gshader',
+        '_ppfx_ssao',
+        '_ppfx_mblur',
+        '_ppfx_bloom_mixdown',
+        '_ppfx_dof',
+        '_uv_distort_map',
+        '_uv_distort_scale',
+        '_uv_distort_absolute',
+        '_ibl_cubemap',
+        '_ssao_radius',
+        '_use_bloom',
+        '_use_dof',
+        '_bloom_grade',
+        '_exposure',
+        '_exposure_accum',
+        '_exposure_bias',
+        '_min_exposure',
+        '_max_exposure',
+        '_fb_luma1x1',
+        '_fb_ssao',
+        '_luma_scale',
+        '_luma_sync',
+        '_pbo_luma',
+        '_auto_exposure',
+        '_auto_exposure_frame',
+        '_exposure_adaption_up',
+        '_exposure_adaption_down',
+        '_pbo_dof',
+        '_dof_sync',
+        '_dof_focus',
+        '_dof_accum',
+        '_dof_range',
+        '_dof_fstop',
+        '_shadowmap',
+        '_light_buffer',
+        '_mblur_target_fps',
+        '_mblur_shutter',
+        '_scatter_range',
+        '_debug_ssao',
+        '_grease',
+        '_ambient_power',
+    ]
+    
+    MIN_EXPOSURE = 5.0*10**-4.0 # 2.0**-4.0
+    
+    def __init__(self):
+        RNode.__init__(self)
+        self.name = 'grenderman'
+        self._use_bloom = True
+        self._debug_ssao = False
+        self._bloom_grade = 1
+        self._use_motion_blur = True
+        self._scene_framebuffer = None
+        self._grading0 = None
+        self._grading1 = None
+        self._grading_mix = 0.0
+        self._ppfx_tonemap = None
+        self._ppfx_gshader = None
+        self._ppfx_ssao = None
+        self._ppfx_mblur = None
+        self._ppfx_dof = None
+        self._ppfx_bloom_mixdown = None
+        self._fb_ssao = None
+        self._fog_power = 0.0
+        self._fog_range = 100.0
+        self._fog_falloff_radius = 128.0
+        self._fog_thickness = 0.0
+        self._fog_color = vec3(0.3, 0.7, 1.0)
+        self._fog_map = None
+        self._fog_uv_scale_offset = vec4(1.0,1.0,0.0,0.0)
+        self._uv_distort_map = None
+        self._uv_distort_scale = vec2(1,1)
+        self._uv_distort_absolute = False
+        # how much bloom will be mixed in
+        self._grease = 1.0/8.0
+        self._ssao_radius = 0.001
+        self._exposure = 0.0 # in EV
+        self._auto_exposure = True
+        self._auto_exposure_frame = 0
+        self._exposure_adaption_up = 1.0 / 1.0
+        self._exposure_adaption_down = 1.0 / 0.3
+        self._exposure_accum = Accum(max_size=3)
+        self._exposure_bias = 0.0
+        self._fb_luma1x1 = None
+        self._min_exposure = -4.0
+        self._max_exposure = 4.0
+        self._ibl_cubemap = None
+        self._light_buffer = None
+        self._use_ssao = False
+        self._ambient_color = vec3(1.0,1.0,1.0)
+        self._ambient_power = 2.0**-5.0
+        self._use_dof = False
+        self._dof_range = vec2(0.1, 100.0)
+        self._dof_fstop = 3.2
+        self._luma_sync = None
+        self._use_fog = False
+        # big enough for a 2x1 or 1x2 RGBAF32 buffer
+        self._pbo_luma = PixelPackBuffer.from_size(2 * 1 * 4 * 4)
+        # 1x1 RGBAF32 
+        self._pbo_dof = PixelPackBuffer.from_size(1 * 1 * 4 * 4)
+        self._dof_sync = None
+        self._scatter_range = None #(50.0, 100.0)
+        self._mblur_target_fps = 24.0
+        self._luma_scale = 1.0
+        self._dof_focus = 1.0
+        self._dof_accum = Accum(max_size=3)
+        # shutter is usually open only half of the time
+        # shutter time of 0.1: virtually no motion blur
+        # shutter time of 0.9: smooth motion
+        self._mblur_shutter = 0.5
+        self._skybox = None
+        self.use_quality_config()
+        
+    @property
+    def skybox(self):
+        return self._skybox
+        
+    @property
+    def use_fog(self):
+        return self._use_fog
+    @use_fog.setter
+    def use_fog(self, value):
+        self._use_fog = value
+        
+    @property
+    def fog_map(self):
+        return self._fog_map
+    @fog_map.setter
+    def fog_map(self, value):
+        self._fog_map = value
+    
+    @property
+    def fog_uv_scale_offset(self):
+        return self._fog_uv_scale_offset
+    @fog_uv_scale_offset.setter
+    def fog_uv_scale_offset(self, value):
+        self._fog_uv_scale_offset = value
+        self.update_gshader()
+        
+    @property
+    def uv_distort_map(self):
+        return self._uv_distort_map
+    @uv_distort_map.setter
+    def uv_distort_map(self, value):
+        self._uv_distort_map = value
+        
+    @property
+    def uv_distort_scale(self):
+        return self._uv_distort_scale
+    @uv_distort_scale.setter
+    def uv_distort_scale(self, value):
+        self._uv_distort_scale = value
+        self.update_tonemap()
+        
+    @property
+    def uv_distort_absolute(self):
+        return self._uv_distort_absolute
+    @uv_distort_absolute.setter
+    def uv_distort_absolute(self, value):
+        self._uv_distort_absolute = value
+        
+    @property
+    def fog_power(self):
+        return self._fog_power
+    @fog_power.setter
+    def fog_power(self, value):
+        self._fog_power = value
+        self.update_gshader()
+    
+    @property
+    def fog_falloff_radius(self):
+        return self._fog_falloff_radius
+    @fog_falloff_radius.setter
+    def fog_falloff_radius(self, value):
+        self._fog_falloff_radius = value
+        self.update_gshader()
+    
+    @property
+    def fog_color(self):
+        return self._fog_color
+    @fog_color.setter
+    def fog_color(self, value):
+        self._fog_color = value
+        self.update_gshader()
+    
+    @property
+    def fog_range(self):
+        return self._fog_range
+    @fog_range.setter
+    def fog_range(self, value):
+        self._fog_range = value
+        self.update_gshader()
+    
+    @property
+    def fog_thickness(self):
+        return self._fog_thickness
+    @fog_thickness.setter
+    def fog_thickness(self, value):
+        self._fog_thickness = value
+        self.update_gshader()
+        
+    @property
+    def ambient_color(self):
+        return self._ambient_color
+    @ambient_color.setter
+    def ambient_color(self, value):
+        self._ambient_color = value
+        self.update_gshader()
+        
+    @property
+    def use_dof(self):
+        return self._use_dof
+    @use_dof.setter
+    def use_dof(self, value):
+        self._use_dof = value
+        
+    @property
+    def fb_ssao(self):
+        return self._fb_ssao
+        
+    @property
+    def bloom_grade(self):
+        return self._bloom_grade
+    @bloom_grade.setter
+    def bloom_grade(self, value):
+        self._bloom_grade = value
+
+    @property
+    def debug_ssao(self):
+        return self._debug_ssao
+    @debug_ssao.setter
+    def debug_ssao(self, value):
+        self._debug_ssao = value
+        
+    @property
+    def scatter_range(self):
+        return self._scatter_range
+    @scatter_range.setter
+    def scatter_range(self, value):
+        assert value is None or len(value) == 2
+        self._scatter_range = value
+        
+    @property
+    def grease(self):
+        return self._grease
+    @grease.setter
+    def grease(self, value):
+        self._grease = value
+        self.update_bloom()
+        
+    @property
+    def use_motion_blur(self):
+        return self._use_motion_blur
+    @use_motion_blur.setter
+    def use_motion_blur(self, value):
+        self._use_motion_blur = value
+        
+    @property
+    def mblur_shutter(self):
+        return self._mblur_shutter
+    @mblur_shutter.setter
+    def mblur_shutter(self, value):
+        self._mblur_shutter = value
+        
+    @property
+    def use_ssao(self):
+        return self._use_ssao
+    @use_ssao.setter
+    def use_ssao(self, value):
+        self._use_ssao = value
+        
+    @property
+    def mblur_target_fps(self):
+        return self._mblur_target_fps
+    @mblur_target_fps.setter
+    def mblur_target_fps(self, value):
+        self._mblur_target_fps = value
+        
+    @property
+    def scene_framebuffer(self):
+        return self._scene_framebuffer
+    
+    @property
+    def light_buffer(self):
+        return self._light_buffer
+        
+    @property
+    def ppfx_gshader(self):
+        return self._ppfx_gshader
+        
+    @property
+    def depth_texture(self):
+        return self._depth_texture
+        
+    @property
+    def ibl_cubemap(self):
+        return self._ibl_cubemap
+    @ibl_cubemap.setter
+    def ibl_cubemap(self, value):
+        self._ibl_cubemap = value
+        
+    @property
+    def min_exposure(self):
+        return self._min_exposure
+    @min_exposure.setter
+    def min_exposure(self, value):
+        self._min_exposure = value
+    
+    @property
+    def max_exposure(self):
+        return self._max_exposure
+    @max_exposure.setter
+    def max_exposure(self, value):
+        self._max_exposure = value
+        
+    @property
+    def exposure_bias(self):
+        return self._exposure_bias
+    @exposure_bias.setter
+    def exposure_bias(self, value):
+        self._exposure_bias = value
+        
+    @property
+    def exposure_adaption_up(self):
+        return self._exposure_adaption_up
+    @exposure_adaption_up.setter
+    def exposure_adaption_up(self, value):
+        self._exposure_adaption_up = value
+    
+    @property
+    def exposure_adaption_down(self):
+        return self._exposure_adaption_down
+    @exposure_adaption_down.setter
+    def exposure_adaption_down(self, value):
+        self._exposure_adaption_down = value
+        
+    @property
+    def auto_exposure(self):
+        return self._auto_exposure
+    @auto_exposure.setter
+    def auto_exposure(self, value):
+        self._auto_exposure = value
+        
+    @property
+    def exposure(self):
+        return self._exposure
+    @exposure.setter
+    def exposure(self, value):
+        self._exposure = value
+        self.update_tonemap()
+        
+    @property
+    def use_bloom(self):
+        return self._use_bloom
+    @use_bloom.setter
+    def use_bloom(self, value):
+        self._use_bloom = value
+          
+    @property
+    def ssao_radius(self):
+        return self._ssao_radius
+    @ssao_radius.setter
+    def ssao_radius(self, value):
+        self._ssao_radius = value
+        self.update_ssao()
+    
+    @property
+    def grading(self):
+        return self._grading0
+    @grading.setter
+    def grading(self, value):
+        self._grading0 = value
+        self._grading1 = None
+        self.grading_mix = 0.0
+        
+    @property
+    def grading0(self):
+        return self._grading0
+    @grading0.setter
+    def grading0(self, value):
+        if value is self._grading0:
+            return
+        self._grading0 = value
+    
+    @property
+    def grading1(self):
+        return self._grading1
+    @grading1.setter
+    def grading1(self, value):
+        if value is self._grading1:
+            return
+        self._grading1 = value
+    
+    @property
+    def grading_mix(self):
+        return self._grading_mix
+    @grading_mix.setter
+    def grading_mix(self, value):
+        if value == self._grading_mix:
+            return
+        self._grading_mix = value
+        self.update_tonemap()
+        
+    @property
+    def dof_range(self):
+        return self._dof_range
+    @dof_range.setter
+    def dof_range(self, value):
+        self._dof_range = value
+        self.update_dof_uniforms()
+    
+    @property
+    def dof_fstop(self):
+        return self._dof_fstop
+    @dof_fstop.setter
+    def dof_fstop(self, value):
+        self._dof_fstop = value
+        self.update_dof_uniforms()
+        
+    @property
+    def dof_focus(self):
+        return self._dof_focus
+    @dof_focus.setter
+    def dof_focus(self, value):
+        self._dof_focus = value
+        self.update_dof_uniforms()
+        
+    @property
+    def ambient_power(self):
+        return self._ambient_power
+    @ambient_power.setter
+    def ambient_power(self, value):
+        self._ambient_power = value
+        self.update_gshader()
+        
+    def update_bloom(self):
+        ppfx = self._ppfx_bloom_mixdown
+        if not ppfx:
+            return
+        ppfx.set_uniform_values(
+            mix_factor = self._grease
+        )
+    
+    def update_dof_uniforms(self):
+        dof = self._ppfx_dof
+        if not dof:
+            return
+        uniforms = dof.uniforms
+        if hasattr(uniforms, 'focalDepth'):
+            uniforms.focalDepth = self._dof_focus
+        if hasattr(uniforms, 'focalDepthRange'):
+            uniforms.focalDepthRange = self._dof_range._ptr[0]
+        if hasattr(uniforms, 'fstop'):
+            uniforms.fstop = self._dof_fstop
+        
+    def update_tonemap(self):
+        tonemap = self._ppfx_tonemap
+        if not tonemap:
+            return
+        uniforms = tonemap.uniforms
+        if hasattr(uniforms, 'exposure'):
+            uniforms.exposure = 2.0**self._exposure
+        if hasattr(uniforms, 'uv_lut_scale'):
+            uniforms.uv_lut_scale = self._uv_distort_scale._ptr[0]
+        if hasattr(uniforms, 'lut_grading_mix'):
+            uniforms.lut_grading_mix = self._grading_mix
+        
+    def update_gshader(self):
+        ppfx = self._ppfx_gshader
+        if not ppfx:
+            return
+        ppfx.set_uniform_values(
+            ambient_power = self._ambient_power,
+            ambient_color = self._ambient_color.to_tuple())
+        if self._use_fog:
+            ppfx.set_uniform_values(
+                fog_power = self._fog_power,
+                fog_thickness = self._fog_thickness,
+                fog_range = self._fog_range,
+                fog_color = self._fog_color.to_tuple(),
+                fog_falloff_radius = self._fog_falloff_radius)
+            if self._fog_map:
+                ppfx.set_uniform_values(
+                    fog_uv_scale_offset = self._fog_uv_scale_offset.to_tuple())
+        
+    def update_ssao(self):
+        ssao = self._ppfx_ssao
+        if not ssao:
+            return
+        ssao.set_uniform_values(
+            radius = self._ssao_radius)
+        
+    def update_uniforms(self):
+        pass
+        
+    def update_all(self):
+        self.update_gshader()
+        
+    def on_frame(self):
+        if self._auto_exposure:
+            self.update_exposure()
+        if self._use_dof:
+            self.update_dof()
+        
+        tm = TimeManager()
+        rm = RenderManager()
+        if tm.adaptive:
+            frame_rate = rm.frame_rate
+        else:
+            frame_rate = tm.framerate
+        
+        ppfx_mblur = self._ppfx_mblur
+        if ppfx_mblur:
+            SAMPLE_COUNT = 16
+            factor = (SAMPLE_COUNT-1) / float(SAMPLE_COUNT)
+            
+            blur_scale = self._mblur_shutter * min(1.0, 
+                frame_rate*factor / self._mblur_target_fps)        
+            ppfx_mblur.set_uniform_value('blur_scale', blur_scale)
+        
+    def use_quality_config(self):
+        quality = Config().videoquality
+        
+        Y = True
+        N = False
+        
+        self.use_motion_blur = [N,N,N,Y,Y,Y,Y][quality]
+        self.use_dof =         [N,N,N,Y,Y,Y,Y][quality]
+        self.use_ssao =        [N,N,N,Y,Y,Y,Y][quality]
+        self.use_bloom =       [N,Y,Y,Y,Y,Y,Y][quality]
+        self.bloom_grade =     [0,4,3,2,1,1,1][quality]
+        
+    def update_dof(self):
+        tm = TimeManager()
+        if tm.frames < 10:
+            return 
+        
+        fb = self._scene_framebuffer
+        w,h = fb.size
+        pbo_dof = self._pbo_dof
+        
+        sync = self._dof_sync
+        if sync is not None:
+            result = glClientWaitSync(sync, 0, 0)
+            if result == GL_CONDITION_SATISFIED or result == GL_ALREADY_SIGNALED:
+                glDeleteSync(sync)
+                sync = None
+                with pbo_dof.map_read() as data:
+                    fdata = render_ffi.cast('unsigned int*',data)
+                    depth32 = fdata[0]
+                # assuming 24bit depth 8bit stencil
+                depth = (depth32 >> 8) / 0xffffff
+                self._dof_accum.push(depth)
+        if sync is None:
+            # write
+            fb.read_pixels_from_depth(pbo_dof, (w//2,h//2,1,1))
+            self._dof_sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0)
+            
+        if not self._dof_accum.values:
+            return
+        depth = self._dof_accum.average()
+        
+        td = TimeManager().timefactor(10.0)
+        focus = self._dof_focus
+        self.dof_focus = focus + (depth - focus)*td
+        
+    def update_exposure(self):
+        if not self._fb_luma1x1:
+            return
+
+        self._auto_exposure_frame += 1
+        if self._auto_exposure_frame < 10:
+            return
+
+        fb_luma1x1 = self._fb_luma1x1
+        pbo_luma = self._pbo_luma
+        
+        sync = self._luma_sync
+        if sync is not None:
+            result = glClientWaitSync(sync, 0, 0)
+            if result == GL_CONDITION_SATISFIED or result == GL_ALREADY_SIGNALED:
+                glDeleteSync(sync)
+                sync = None
+                self._luma_sync = None
+                fbsize = fb_luma1x1.size 
+                count = fbsize[0] * fbsize[1] 
+                assert count <= 2
+                with pbo_luma.map_read() as data:
+                    fdata = render_ffi.cast('float*',data)
+                    col = vec3(*fdata[0:3])
+                    if count > 1:
+                        col = col.add(vec3(*fdata[4:7])).div_f(2)
+                
+                col = vec3(0.299, 0.587, 0.114).dot(col) * self._luma_scale
+                
+                minv = self.MIN_EXPOSURE
+                luma = max(col, minv)
+                blevel = math.log(luma) / math.log(2.0)
+                blevel = clamp(blevel-self._exposure_bias, self._min_exposure, self._max_exposure)
+                
+                self._exposure_accum.push(blevel)
+        if sync is None:
+            # write
+            fb_luma1x1.read_pixels(pbo_luma)
+            self._luma_sync = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0)
+        
+        if not self._exposure_accum.values:
+            return            
+        blevel = self._exposure_accum.average()        
+        
+        target = clamp(-blevel+self._exposure_bias, self._min_exposure, self._max_exposure)
+
+        old_exp = int(self.exposure*100.0)        
+        
+        td = TimeManager().timedelta
+        if self.exposure < target:
+            self.exposure = min(self.exposure + self._exposure_adaption_up*td, target)
+        else:
+            self.exposure = max(self.exposure - self._exposure_adaption_down*td, target)
+        
+        new_exp = int(self.exposure*100.0)
+        if 0 and old_exp != new_exp:
+            print(self.exposure, target)
+    
+    def ensure_passes(self):
+        if self._scene_framebuffer:
+            return
+        self.create_passes()
+    
+    if 0:
+        def inject_camera(self, camera):
+            assert self not in camera.ppfx_group.passes, \
+                "already assigned to camera"
+            self.ensure_passes()
+            self.framebuffer = camera.framebuffer
+            camera.framebuffer = self.scene_framebuffer
+            camera.light_buffer = self.light_buffer
+            camera.clear_stencil = True
+            camera.ppfx_group.add_pass(self)
+    
+    def _create_ssao_pass(self, p0, normal_texture, depth_texture):
+        fw,fh = p0.size
+        
+        if Config().videoquality < VideoQuality.HIGH:
+            ssao_blur_size = fw//2,fh//2
+            ssao_size = fw//2,fh//2
+        else:
+            ssao_size = ssao_blur_size = fw,fh
+            
+        # SSAO pass            
+        fb_ssao = Framebuffer()
+        fb_ssao.name = 'fb-ssao'
+        ssao_texture = FramebufferTexture2D(*ssao_size)
+        ssao_texture.name = 'ssao'
+        ssao_texture.use_r8()
+        ssao_texture.bilinear = True            
+        fb_ssao.add_color_attachment(ssao_texture)
+
+        #if 1:
+        #    RenderManager().debug_color_texture(ssao_texture)
+
+        ppfx = PPFX('g/ppfx/ssao')
+        ppfx.name = 'ppfx-ssao'
+        ppfx.add_shader_texture('fb_color2', normal_texture)
+        ppfx.add_shader_texture('fb_depth', depth_texture)
+        self._ppfx_ssao = ppfx
+        
+        tw,th = depth_texture.size
+        ppfx.set_uniform_values(
+            fb_size = (tw,th),
+            fb_texel = (1.0/tw,1.0/th),
+            radius = self._ssao_radius)
+        
+        self << fb_ssao << ppfx
+        
+        SSAO_BLUR = True
+        
+        if SSAO_BLUR:
+            fb_ssao_blur = Framebuffer()
+            fb_ssao_blur.name = 'fb-ssao-blur'
+            fb_ssao_blur_texture = FramebufferTexture2D(*ssao_blur_size)
+            fb_ssao_blur_texture.name = 'ssao-blur'
+            fb_ssao_blur_texture.use_r8()
+            fb_ssao_blur_texture.bilinear = True
+            fb_ssao_blur.add_color_attachment(fb_ssao_blur_texture)
+    
+            ppfx = PPFX('ppfx/blur',defines=dict(
+                KERNEL_RADIUS = 3))
+            ppfx.add_framebuffer_textures(fb_ssao)
+            ppfx.name = 'ppfx-ssao-blur'
+            
+            self << fb_ssao_blur << ppfx
+        
+            return fb_ssao_blur
+        else:
+            return fb_ssao
+        
+    def _create_dof_mblur_pass(self, p0, depth_texture, velocity_texture):
+        fw,fh = p0.size
+        
+        size = fw,fh
+        
+        # dof pass            
+        fb = Framebuffer()
+        fb.name = 'fb-dof-mblur'
+        dof_texture = FramebufferTexture2D(*size)
+        dof_texture.name = 'dof-mblur'
+        dof_texture.use_rgb16F()
+        dof_texture.bilinear = True
+        dof_texture.wrap = TextureWrap.ClampToBorder            
+        fb.add_color_attachment(dof_texture)
+
+        use_mblur = self._use_motion_blur and velocity_texture is not None
+        use_dof = self._use_dof and depth_texture is not None
+
+        ppfx = PPFX('g/ppfx/dof-mblur', defines=dict(
+            USE_MBLUR = use_mblur,
+            USE_DOF = use_dof
+        ))
+        ppfx.name = 'ppfx-dof-mblur'
+        ppfx.add_framebuffer_textures(p0)
+        if use_mblur:
+            ppfx.add_shader_texture('depth_texture',
+                depth_texture)
+            self._ppfx_mblur = ppfx
+        if use_dof:
+            ppfx.add_shader_texture('tex_velocity', velocity_texture)
+            self._ppfx_dof = ppfx
+        
+        self << fb << ppfx
+        
+        return fb
+    
+    def _create_ssgi_pass(self, p0, pass_id):
+        fw,fh = p0.size
+        
+        ssgi_size = fw,fh
+        
+        # ssgi pass            
+        fb_ssgi = Framebuffer()
+        fb_ssgi.name = 'fb-ssgi'
+        ssgi_texture = FramebufferTexture2D(*ssgi_size)
+        ssgi_texture.name = 'ssgi'
+        ssgi_texture.use_rgb16F()
+        ssgi_texture.bilinear = True
+        ssgi_texture.wrap = TextureWrap.ClampToBorder            
+        fb_ssgi.add_color_attachment(ssgi_texture)
+
+        ppfx = PPFX('g/ppfx/ssgi', defines = dict(
+            SSGI_PASS_ID = pass_id))
+        ppfx.name = 'ppfx-ssgi'
+        ppfx.add_framebuffer_textures(self._scene_framebuffer)
+        ppfx.add_framebuffer_textures(p0, 'combined')
+        #self._ppfx_ssgi = ppfx
+        
+        ppfx.material.set_uniform_values(
+            radius = self._ssao_radius)
+        
+        self << fb_ssgi << ppfx
+        
+        return fb_ssgi
+
+    def _create_exposure_pass(self, fb, level_count):
+        self._fb_luma1x1 = fb
+        self._luma_scale = level_count-1
+    
+    def _create_bloom_pass(self, p0, depth_texture):
+        pO = p0
+        tx,ty = p0.size
+        
+        bloom_grade = self._bloom_grade
+        
+        # pow2 ceiling resolution 
+        f2x = 1<<(tx-1).bit_length()
+        f2y = 1<<(ty-1).bit_length()
+        
+        def make_bloom_fb(w, h, linear=True):
+            fb = Framebuffer()
+            fb.name = 'bloom-{}'.format(max(w,h))
+            color_texture = FramebufferTexture2D(w, h)
+            color_texture.name = 'bloom-{}'.format(max(w,h))
+            if min(w,h) == 1:
+                color_texture.use_rgb32F()
+            else:
+                color_texture.use_rgb16F()
+            color_texture.wrap = TextureWrap.ClampToEdge
+            color_texture.bilinear = linear
+            fb.add_color_attachment(color_texture)
+            return fb
+        
+        blur_shader_upscale = Program.from_files('ppfx/blur',  
+            defines = dict(KERNEL_RADIUS=0, SCALE_UV=True))
+        blur_shader_down = Program.from_files('ppfx/blur',  
+            defines = dict(KERNEL_RADIUS=2, DOWNSCALE=True))
+
+        w,h = f2x,f2y
+        w >>= bloom_grade
+        h >>= bloom_grade
+        
+        if tx != f2x or ty != f2y:            
+            # scale to pow^2 texture
+            ppfx = PPFX(shader=blur_shader_upscale)
+            ppfx.set_uniform_values(
+                    aspect = (f2x / tx, f2y / ty)
+            )
+            ppfx.add_framebuffer_textures(p0)
+            p0 = make_bloom_fb(w,h)
+            p0.name = 'bloom-upscale-pow2'
+            
+            self << p0 << ppfx
+        
+        textures = []
+        
+        # scale down until we hit minimum image size
+        while w > 1 and h > 1:
+            # to properly sample 4x4 kernel
+            size = w,h
+            w >>= 1
+            h >>= 1
+            ppfx = PPFX(shader=blur_shader_down)
+            ppfx.add_framebuffer_textures(p0, size=size)
+            p0 = make_bloom_fb(w,h)
+            textures.append(p0)
+            
+            self << p0 << ppfx
+            
+        if self._auto_exposure: # use last texture as luma source
+            self._create_exposure_pass(textures[-1], len(textures))
+            
+        blur_shader_up = Program.from_files('ppfx/blur',  
+            defines = dict(KERNEL_RADIUS=3, BLUR_ADD=True))
+        blur_shader_up_final = Program.from_files('ppfx/blur',  
+            defines = dict(KERNEL_RADIUS=3, BLUR_ADD=True, 
+                SCALE_COLOR = 1.0 / len(textures),
+                SCALE_UV=True))
+        
+        p0 = textures.pop()
+        # scale up and add up textures
+        while textures:
+            p0_next = textures.pop()
+            # to properly sample 4x4 kernel
+            last_size = w,h
+            w <<= 1
+            h <<= 1
+            last_pass = not textures
+            if last_pass:
+                ppfx = PPFX(shader=blur_shader_up_final)
+                ppfx.set_uniform_values(
+                    aspect = (tx / f2x, ty / f2y),
+                    mix_factor = self._grease,
+                    depth_range = self._scatter_range
+                )
+                ppfx.add_shader_texture('depth_texture',
+                    depth_texture)
+            else:
+                ppfx = PPFX(shader=blur_shader_up)
+            ppfx.add_framebuffer_textures(p0, size=last_size)
+            assert p0_next.size == (w,h)
+            ppfx.add_framebuffer_textures(p0_next, 'fb_add')
+            p0 = make_bloom_fb(w,h)
+            self << p0 << ppfx
+            
+            if last_pass:
+                break
+        
+        ppfx = PPFX('ppfx/bloom_mixdown', 
+            defines = dict(
+                SCALE_WITH_DEPTH = True if self._scatter_range else False
+        ))
+        self._ppfx_bloom_mixdown = ppfx
+        ppfx.set_uniform_values(
+            mix_factor = self._grease
+        )
+        if self._scatter_range:
+            ppfx.set_uniform_values(
+                depth_range = self._scatter_range
+            )
+        ppfx.add_framebuffer_textures(pO)
+        ppfx.add_framebuffer_textures(p0, 'bloom')
+        ppfx.add_shader_texture('depth_texture',
+            depth_texture)
+        p0 = Framebuffer()
+        p0.name = 'fb-bloom-mixdown'
+        mixdown_texture = FramebufferTexture2D(*pO.size)
+        mixdown_texture.name = 'motion-blurred'
+        mixdown_texture.use_rgb16F()
+        mixdown_texture.bilinear = True
+        mixdown_texture.wrap = TextureWrap.ClampToEdge
+        p0.add_color_attachment(mixdown_texture)
+        
+        self << p0 << ppfx
+        
+        return p0
+    
+    def create_passes(self):
+        assert not self._scene_framebuffer, "passes already created"
+        
+        xdir = vec2(1,0)
+        ydir = vec2(0,1)
+        
+        blur_shaders = {}
+        def get_blur_shader(*args, **kargs):
+            key = (args,tuple(kargs.items()))
+            shader = blur_shaders.get(key, None)
+            if shader is None:
+                shader = blur_pass_shader(*args, **kargs)
+                blur_shaders[key] = shader
+            return shader
+                
+        from .core import RenderManager
+        rm = RenderManager()
+        fb_template = rm.framebuffer
+        
+        ppfx = None
+        
+        #
+        # short summary description of deferred renderer:
+        #
+        # GBuffer layout (24 bytes / pixel):
+        #                  |             |             |             |
+        # A -> 8: Albedo R | 8: Albedo G | 8: Albedo B | 8: AO       |        
+        # E -> 8: Roughness| 8: Metallic |
+        # B -> 16F:Normal U              | 16F: Normal V
+        # C -> 16F: Light R              | 16F: Light G              | 16F: Light B      | 16F: Light A
+        # D -> 8I: Vel. X  | 8I: Vel. Y
+        # X -> 24F: Depth                              | 8: Stencil
+        #
+        # 0) each light configures its own (VSM) shadow map target if applicable; 
+        #    enabled objects will render VBO into targets with special material. 
+        #
+        # a) scene rasterizer writes to A, B, D, X
+        #    additive and emissive materials additionally write to C
+        #
+        # b) lights read A, B, X, shadowmap, write diff+spec to C; 
+        #    albedo is premultiplied.
+        #
+        # c) SSAO shader reads B, X, writes to R8 target. also, 5x5 separable box filter
+        #    is applied to result, no edge detection for now.
+        #   
+        # d) mixdown shader reads and combines A, B, C, X, ssao, writes to final RGB16F target
+        #    also does diff+spec IBL / ambient lighting from cubemap,
+        #    and, inexplicably, fogging.
+        #
+        # e) motion blur shader reads D, does 8 fetches on mixdown, 
+        #    writes motion blurred mixdown.
+        #
+        # f) multi-tap blur does 3x3 separated gauss on mb mixdown,
+        #    from 1/2 down to 1/128 sized rendertargets; then upsamples
+        #    back from 1/64 to 1/1 (again 3x3 sep. gauss), adding all resolutions
+        #    together. all this is done in RGB16F and has no visible banding.
+        #
+        # g) tonemap shader combines (mb mixdown + blur texture)/2,
+        #    applies filmic, clamp, gamma 2.2, 32^3 LUT color grading
+        #    (i end up using a passthrough bc the colors already look great),
+        #    calcs luma, dithers 12bit to 8bit (4x4 bayer), then stores
+        #    RGB8 + luma in alpha
+        # 
+        # h) high quality FXAA reads RGBA8, writes antialiased texture.
+        #
+        
+        fw,fh = fb_template.size
+        p0 = Framebuffer()
+        # albedo R,G,B, AO 
+        albedo_texture = FramebufferTexture2D(fw, fh)
+        albedo_texture.name = 'gbuffer-albedo-uvlight'
+        albedo_texture.use_rgba8()
+        albedo_texture.bilinear = False
+        albedo_texture.wrap = TextureWrap.MirroredRepeat
+        p0.add_color_attachment(albedo_texture)
+        
+        # roughness, metallic
+        matid_texture = FramebufferTexture2D(fw, fh)
+        matid_texture.name = 'material-fx'
+        matid_texture.wrap = TextureWrap.MirroredRepeat
+        matid_texture.use_rg8()
+        matid_texture.bilinear = False
+        p0.add_color_attachment(matid_texture)
+        
+        # rg = normal x/y
+        normal_texture = FramebufferTexture2D(fw, fh)
+        normal_texture.name = 'gbuffer-normal'
+        normal_texture.wrap = TextureWrap.ClampToEdge
+        normal_texture.use_rg16()
+        normal_texture.bilinear = False
+        p0.add_color_attachment(normal_texture)
+
+        light_texture = FramebufferTexture2D(fw, fh)
+        light_texture.name = 'gbuffer-light'
+        light_texture.use_rgba16F()
+        light_texture.bilinear = False
+        p0.add_color_attachment(light_texture)
+        #rm = RenderManager()
+        #rm.debug_color_texture(light_texture)
+        
+        if self._use_motion_blur:
+            # rg = velocity x/y
+            velocity_texture = FramebufferTexture2D(fw, fh)
+            velocity_texture.name = 'gbuffer-velocity'
+            velocity_texture.use_rg8i()
+            velocity_texture.bilinear = False
+            p0.add_color_attachment(velocity_texture)
+        else:
+            velocity_texture = None
+
+        depth_texture = FramebufferTexture2D(fw, fh)
+        depth_texture.name = 'gbuffer-depth'
+        depth_texture.use_depth24_stencil8()
+        p0.depth_attachment = depth_texture
+
+        p0.name = 'fb-scene'
+        self._scene_framebuffer = p0
+        
+        self._skybox = Skybox()
+        p0 << self._skybox
+        
+        rm = RenderManager()
+        
+        DEPTH_TEXTURE = depth_texture
+        
+        if 1:
+            #rm.debug_normal_texture(normal_texture)
+            #rm.debug_ycocg_texture(albedo_texture)
+            #rm.debug_color_texture(albedo_texture)
+            #rm.debug_color_texture(matid_texture)
+            #rm.debug_color_texture(light_texture)
+            #rm.debug_color_texture(fx_texture)
+            #rm.debug_depth_texture(depth_texture)
+            #rm.debug_depth_texture(depth_texture)
+            #rm.debug_velocity_texture(velocity_texture)
+            pass
+            
+        node_clear = RClearNode()
+        node_clear.stencil = True
+        node_clear.name = 'clear-scene-fb'
+        self << self.scene_framebuffer << node_clear
+            
+        if self._use_ssao:
+            fb_ssao = self._create_ssao_pass(p0, normal_texture, depth_texture)
+            self._fb_ssao = fb_ssao
+        else:
+            fb_ssao = None 
+        
+        fb_light = Framebuffer()
+        fb_light.name = 'fb-light'
+        fb_light.add_color_attachment(light_texture)
+        fb_light.depth_attachment = depth_texture
+        self._light_buffer = fb_light
+        if 0:
+            rm.debug_color_texture(fb_light.color_attachments[0])
+        self << fb_light
+        
+        gs_defines = {}
+        if fb_ssao:
+            gs_defines['USE_SSAO'] = True
+            gs_defines['DEBUG_SSAO'] = self._debug_ssao
+        if self._use_fog:
+            gs_defines['USE_FOG'] = True
+            if self._fog_map:
+                gs_defines['USE_FOG_TEXTURE'] = True
+        if self._ibl_cubemap:
+            gs_defines['USE_IBL'] = True
+        ppfx = PPFX('g/ppfx/gshader',defines=gs_defines)
+        ppfx.name = 'ppfx-gshader'
+        ppfx.add_framebuffer_textures(p0)
+        self._ppfx_gshader = ppfx
+        if self._ibl_cubemap:
+            ppfx.add_shader_texture('ibl_cubemap', self._ibl_cubemap)
+        if self._use_fog and self._fog_map:
+            ppfx.add_shader_texture('fog_map', self._fog_map)
+        if fb_ssao:
+            ppfx.add_framebuffer_textures(fb_ssao, basename = 'ssao')
+        
+        p0 = Framebuffer.from_template(fb_template, support_hdr=True)
+        p0.color_attachments[0].bilinear = True
+        p0.color_attachments[0].wrap = TextureWrap.ClampToBorder
+        p0.name = ppfx.name
+
+        self << p0 << ppfx
+        
+        if 0:
+            p0 = self._create_ssgi_pass(p0, 0.0)
+        
+        if Config().stereo_mode != StereoMode.OCULUS_RIFT:
+            if self._use_dof or self._use_motion_blur:
+                p0.color_attachments[0].wrap = TextureWrap.ClampToEdge
+                p0 = self._create_dof_mblur_pass(p0, 
+                    DEPTH_TEXTURE, velocity_texture)
+            
+        if self._use_bloom:
+            p0 = self._create_bloom_pass(p0, DEPTH_TEXTURE)
+              
+        TimeManager().event_frame.add(callproxy(self.on_frame))
+        
+        ppfx = PPFX('ppfx/tonemap', defines=dict(
+            USE_GRADING = self._grading0 is not None,
+            ANIMATE_GRADING = self._grading1 is not None,
+            USE_UV_LUT = self._uv_distort_map is not None,
+            UV_LUT_ABSOLUTE = bool(self._uv_distort_absolute)
+        ))
+        self._ppfx_tonemap = ppfx
+        ppfx.add_framebuffer_textures(p0)
+        if self._grading0:
+            ppfx.add_shader_texture('lut_grading0', self._grading0)
+        if self._grading1:
+            ppfx.add_shader_texture('lut_grading1', self._grading1)
+        if self._uv_distort_map:
+            ppfx.add_shader_texture('uv_lut', self._uv_distort_map)
+            
+        fbvp = RenderManager().framebuffer_viewport            
+        fbvp << ppfx
+        
+        self.update_all()
+        
+################################################################################
+
+def try_assign(data, attr, value):
+    if not hasattr(data, attr):
+        return
+    setattr(data, attr, value)
+
+# material for deferred rendering, uses GShader
+class GMaterial(Material):
+    #gshaders = weakref.WeakValueDictionary()
+
+    DEFAULT_SOURCE = 'g/gshader'
+    
+    __slots__ = [
+        '_albedo',
+        '_emissive_color',
+        '_emissive',
+        '_roughness_factor',
+        '_roughness_bias',
+        '_metallic_factor',
+        '_gmat_attribs',
+    ]
+    
+    def __init__(self, shader):
+        Material.__init__(self, shader)
+        self._albedo = vec3(1,1,1)
+        self._emissive_color = vec3(1,1,1)
+        self._emissive = 0.0
+        self._roughness_factor = 1.0
+        self._roughness_bias = 0.0
+        self._metallic_factor = 1.0
+        self._gmat_attribs = self.new_uniform_buffer('GMaterialAttribs')
+        
+    @classmethod
+    def new(cls, source_name=None, **kargs):
+        if source_name is None:
+            source_name = cls.DEFAULT_SOURCE
+        particles = kargs.pop('particles',False)
+        gamma2 = kargs.pop('gamma2',False)
+        additive = kargs.pop('additive',False)
+        billboard = kargs.pop('billboard',False)
+        quilting = kargs.pop('quilting',False)
+        fluorescent = kargs.pop('fluorescent',False)
+        discard_albedo_alpha = kargs.pop('discard_albedo_alpha',False)
+        discard_glow_alpha = kargs.pop('discard_glow_alpha',False)
+        albedo_map = kargs.pop('albedo_map',None)
+        vertex_color = kargs.pop('vertex_color',False)
+        gloss_map = kargs.pop('gloss_map',None)
+        glow_map = kargs.pop('glow_map',None)
+        derivative_map = kargs.pop('derivative_map', False)
+        height_map = kargs.pop('height_map',None)
+        height_map_factor = kargs.pop('height_map_factor',None)
+        shadow_light_source = kargs.pop('shadow_light_source',None)
+        if Config().videoquality < VideoQuality.HIGH:
+            height_map = None
+        use_backface_normals = kargs.pop('use_backface_normals', False)
+        defines = kargs.pop('defines',{})
+        if gamma2:
+            defines['USE_GAMMA2'] = True
+        if particles:
+            defines['USE_PARTICLES'] = True
+        if vertex_color:
+            defines['USE_VERTEX_COLOR'] = True
+        if use_backface_normals:
+            defines['USE_BACKFACE_NORMALS'] = True
+        if discard_albedo_alpha:
+            defines['DISCARD_ALBEDO_ALPHA'] = True
+        if discard_glow_alpha:
+            defines['DISCARD_GLOW_ALPHA'] = True
+        if additive:
+            defines['USE_ADDITIVE'] = True
+        if quilting:
+            defines['USE_QUILTING'] = True
+        if billboard:
+            defines['USE_BILLBOARD'] = True
+        from .texture import ImageTexture2DArray
+        if height_map is not None:
+            defines['USE_HEIGHT_MAP'] = True
+            if derivative_map:
+                defines['USE_DERIVATIVE_MAP'] = True
+            if height_map_factor is not None:
+                defines['HEIGHT_MAP_FACTOR'] = height_map_factor
+            if isinstance(height_map, ImageTexture2DArray):
+                defines['USE_TEXTURE_ARRAY'] = True
+        if gloss_map is not None:
+            defines['USE_GLOSS_MAP'] = True 
+            if isinstance(gloss_map, ImageTexture2DArray):
+                defines['USE_TEXTURE_ARRAY'] = True
+        if glow_map is not None:
+            defines['USE_GLOW_MAP'] = True     
+            if isinstance(glow_map, ImageTexture2DArray):
+                defines['USE_TEXTURE_ARRAY'] = True
+        if albedo_map is not None:
+            defines['USE_ALBEDO_MAP'] = True
+            if isinstance(albedo_map, ImageTexture2DArray):
+                defines['USE_TEXTURE_ARRAY'] = True
+        kargs['defines'] = defines
+        material = cls.from_files(source_name, **kargs)
+        material.use_backface_culling = not use_backface_normals 
+        if additive:
+            material.set_additive()
+            material.renderpass = 200 # must be after skybox
+        shadow_material = None
+        if shadow_light_source:
+            defines = dict(defines)
+            defines.update(shadow_light_source.get_shadow_material_defines())
+            defines['SHADOW_MATERIAL'] = True
+            kargs['defines'] = defines
+            kargs['use_geometry'] = True
+            shadow_material = Material.from_files(source_name, **kargs)
+            shadow_light_source.add_shadow_material(shadow_material)
+            material.use_shadow = True
+            material.shadow_material = shadow_material 
+            shadow_material.use_backface_culling = not use_backface_normals 
+            
+        if albedo_map is not None:
+            material.add_shader_texture('albedo_map', albedo_map)
+            if shadow_material and discard_albedo_alpha:
+                shadow_material.add_shader_texture('albedo_map', albedo_map)
+                
+        if height_map is not None:
+            material.add_shader_texture('height_map', height_map)
+        if gloss_map is not None:
+            material.add_shader_texture('gloss_map', gloss_map)
+        if glow_map is not None:
+            material.add_shader_texture('glow_map', glow_map)
+            if shadow_material and discard_glow_alpha:
+                shadow_material.add_shader_texture('glow_map', glow_map)
+                
+        return material
+    
+    
+    def invalidate_uniforms(self):
+        RenderManager().invalidate_uniforms(self)
+
+    def update_uniforms(self):
+        attribs = self._gmat_attribs
+        with attribs.map_write() as data:     
+            data.albedo = self._albedo._ptr[0]
+            data.emissive_color = self._emissive_color._ptr[0] 
+            data.emissive = self._emissive
+            data.roughness_bias = self._roughness_bias
+            data.roughness_factor = self._roughness_factor
+            data.metallic_factor = self._metallic_factor
+    
+    def ensure_compiled(self):
+        Material.ensure_compiled(self)
+        self.update_uniforms()
+        
+    @property
+    def emissive_color(self):
+        return self._emissive_color
+    @emissive_color.setter
+    def emissive_color(self, value):
+        self._emissive_color = value
+        self.invalidate_uniforms()
+        
+    @property
+    def albedo(self):
+        return self._albedo
+    @albedo.setter
+    def albedo(self, value):
+        self._albedo = value
+        self.invalidate_uniforms()
+    
+    @property
+    def emissive(self):
+        return self._emissive
+    @emissive.setter
+    def emissive(self, value):
+        self._emissive = value
+        self.invalidate_uniforms()
+        
+    @property
+    def roughness_bias(self):
+        return self._roughness_bias
+    @roughness_bias.setter
+    def roughness_bias(self, value):
+        self._roughness_bias = value
+        self.invalidate_uniforms()
+
+    @property
+    def roughness_factor(self):
+        return self._roughness_factor
+    @roughness_factor.setter
+    def roughness_factor(self, value):
+        self._roughness_factor = value
+        self.invalidate_uniforms()
+    
+    @property
+    def metallic_factor(self):
+        return self._metallic_factor
+    @metallic_factor.setter
+    def metallic_factor(self, value):
+        self._metallic_factor = value
+        self.invalidate_uniforms()
+
+################################################################################
+
+class GMaterialSDF(GMaterial):
+    DEFAULT_SOURCE = 'g/gshader_sdf'
+    
+    __slots__ = [
+        '_camera',
+    ]
+    
+    def __init__(self, shader):
+        GMaterial.__init__(self, shader)
+        self._camera = None
+    
+    @property
+    def camera(self):
+        return self._camera
+    @camera.setter
+    def camera(self, value):
+        self._camera = value
+
+    def update_uniforms(self):
+        GMaterial.update_uniforms(self)
+        from .system import System
+        w = System().screensize.x
+        camera = self._camera
+        fov = camera.fov if self._camera else 90.0
+        f = (2.0**0.5) * 0.5 # fit square into circle
+        texel_size = math.tan(math.radians(fov)/2.0) * f / w
+        self.set_uniform_values(
+            texel_size = texel_size
+        )
+
+################################################################################
+
+if 0:
+    class GRNode(RNode):
+        __slots__ = [
+            'skybox',
+            'hdrm',
+        ]
+        
+        def __init__(self):
+            RNode.__init__(self)
+            self.sources.add(Skybox())
+            
+            # must be configured by user before inserting first camera
+            hdrm = HDRPPFXManager()
+            self.hdrm = hdrm
+            
+        def _deep_child_added(self, obj):
+            Scene._deep_child_added(self, obj)
+            if isinstance(obj, Camera):
+                self.hdrm.inject_camera(obj)
+            if isinstance(obj, DirectionalLight):
+                obj.fb_ssao = self.hdrm.fb_ssao
+
+################################################################################

          
A => liminal_legacy/liminal/engine/draw.py +547 -0
@@ 0,0 1,547 @@ 
+from __future__ import (print_function, division, absolute_import)
+
+import math
+import logging
+import random
+
+from glm import vec2, vec3, vec4, X_AXIS, Y_AXIS, Z_AXIS, mat3, AABB
+from gl import (GL_LINES, GL_TRIANGLES)
+
+from .interface import Named
+from .mesh import MeshBuffer, VertexArray, MeshAttribute
+from .render import AttribLocations, RNode, RJCMD_DRAW
+from glue import mix
+
+################################################################################
+
+log = logging.getLogger('liminal.debugdraw')
+
+################################################################################
+
+def random_colors(count):
+    for i in range(count):
+        yield random_color()
+
+def random_color():
+    return vec4(
+        random.random(),
+        random.random(),
+        random.random(),
+        1.0,
+    )
+
+################################################################################
+
+class VertexBufferPainter(RNode):
+    __slots__ = [
+        '_config',
+        '_vertex_offset',
+        '_mesh_buffer',
+        '_mesh',
+        '_material',
+    ]
+
+    RED = vec4(1,0,0,1)
+    GREEN = vec4(0,1,0,1)
+    BLUE = vec4(0,0,1,1)
+    YELLOW = vec4(1,1,0,1)
+    CYAN = vec4(0,1,1,1)
+    MAGENTA = vec4(1,0,1,1)
+    BLACK = vec4(0,0,0,1)
+    WHITE = vec4(1,1,1,1)
+
+    VERTEX_LIMIT = 1024
+    DRAW_ARRAY_MODE = GL_TRIANGLES
+    
+    POSITION_BUFFER_NAME = 'in_Position'
+    
+    def __init__(self, **kargs):
+        RNode.__init__(self)
+        
+        name = kargs.pop('name', 'vertex_buffer_painter')
+        config = kargs.pop('config', 'pc')
+        vertex_limit = kargs.pop('vertex_limit', self.VERTEX_LIMIT)
+        
+        self.name = name
+        self._config = config
+        self._vertex_offset = 0
+        
+        self._mesh_buffer = MeshBuffer.from_format(config, vertex_limit)
+        self._mesh = VertexArray.from_mesh_buffer(self._mesh_buffer)
+        self._mesh.draw_array_mode = self.DRAW_ARRAY_MODE
+        
+    @property
+    def material(self):
+        return self._material
+    @material.setter
+    def material(self, value):
+        self._material = value
+
+    @property
+    def vertex_array(self):
+        return self._mesh
+
+    @property
+    def mesh_buffer(self):
+        return self._mesh_buffer
+
+    def add_vertices(self, *vectorlists):
+        """add_vertices([vec3,...], [...], ...)"""
+        
+        mesh_buffer = self._mesh_buffer        
+        mesh_format = mesh_buffer.format
+        
+        vertex_offset = self._vertex_offset 
+        
+        count = len(vectorlists[0])
+        if (vertex_offset + count) > mesh_buffer.vertex_capacity:
+            vertex_offset = 0
+        
+        mesh_format.write_list(mesh_buffer, vertex_offset, *vectorlists)
+        self._mesh.update_from_mesh_buffer(mesh_buffer)
+        
+        self._vertex_offset = vertex_offset + count
+        
+    def process(self, batch, jobs):
+        self._mesh.process(batch, jobs)
+        
+    def clear(self):
+        self._mesh_buffer.vertex_count = 0
+        self._mesh.update_from_mesh_buffer(self._mesh_buffer)
+        self._vertex_offset = 0
+
+################################################################################
+
+class PolygonPainter(VertexBufferPainter):
+    __slots__ = [
+        'convex_polygon'
+    ]
+    
+    DRAW_ARRAY_MODE = GL_TRIANGLES
+
+    def __init__(self, **kargs):
+        VertexBufferPainter.__init__(self, name = 'polygon_painter', **kargs)
+        
+    def convex_polygon(self, *lists):
+        """convex_polygon([vec3,...], [...], ...)"""
+        
+        new_lists = []
+        for items in lists:
+            new_items = []
+            for i in range(1, len(items)-1):
+                new_items.append(items[0])
+                new_items.append(items[i])
+                new_items.append(items[i+1])
+            new_lists.append(new_items)
+            
+        self.add_vertices(*new_lists)
+
+    QUAD_TEXCOORDS = (
+        vec2(0.0, 0.0),
+        vec2(1.0, 0.0),
+        vec2(1.0, 1.0),
+        vec2(0.0, 1.0),
+    )
+    
+    def rect_vec4(self, rect, color, *lists):
+        if not isinstance(color, (list, tuple)):
+            color = [color]*4
+        self.convex_polygon((
+            vec3(rect.x, rect.y, 0.0),
+            vec3(rect.x+rect.z, rect.y, 0.0),
+            vec3(rect.x+rect.z, rect.y+rect.w, 0.0),
+            vec3(rect.x, rect.y+rect.w, 0.0),
+        ), color, *lists)
+            
+    def quadplane(self, origin, vx, vy, color, *lists):
+        vox = origin.add(vx)
+        self.convex_polygon((
+            origin,
+            vox,
+            vox.add(vy),
+            origin.add(vy),
+        ), [color]*4, *lists)
+
+    def screen(self, fov=math.pi, distance=1.0, height=1.0):
+        # needs pctn format
+        s = math.tan(fov*0.5)*distance
+        c = distance        
+        colors = (vec4(1,1,1,1),)*4
+        normals = (vec3(0.0,-1.0,0.0),)*4
+        self.convex_polygon((
+            vec3(-s,c,-height*0.5),
+            vec3(s,c,-height*0.5),
+            vec3(s,c,height*0.5),
+            vec3(-s,c,height*0.5),
+        ), colors, (
+            vec2(0.0, 0.0),
+            vec2(1.0, 0.0),
+            vec2(1.0, 1.0),
+            vec2(0.0, 1.0),
+        ), normals)
+        
+
+    def hemispheric_screen(self, fov=math.pi, radius=1.0, height=1.0, segments=24):
+        # needs pctn format
+        # height = fov*radius/aspect
+        # hemispheric screen
+        begin_angle = -fov*0.5
+        end_angle = fov*0.5
+        colors = (vec4(1,1,1,1),)*4
+        for i in range(segments):
+            u0 = i/segments
+            u1 = (i+1)/segments
+            a0 = mix(begin_angle, end_angle, u0)
+            a1 = mix(begin_angle, end_angle, u1)
+            s0,c0 = math.sin(a0),math.cos(a0)
+            s1,c1 = math.sin(a1),math.cos(a1)
+            ps0,pc0 = s0*radius,c0*radius
+            ps1,pc1 = s1*radius,c1*radius            
+            self.convex_polygon((
+                vec3(ps0,pc0,-height*0.5),
+                vec3(ps1,pc1,-height*0.5),
+                vec3(ps1,pc1,height*0.5),
+                vec3(ps0,pc0,height*0.5),
+            ), colors, (
+                vec2(u0, 0.0),
+                vec2(u1, 0.0),
+                vec2(u1, 1.0),
+                vec2(u0, 1.0),
+            ), (
+                vec3(-s0,-c0,0.0),
+                vec3(-s1,-c1,0.0),
+                vec3(-s1,-c1,0.0),
+                vec3(-s0,-c0,0.0),
+            ))
+
+    def aabb(self, aabb, colors, wallflags = 0x3f):
+        texcoords = self.QUAD_TEXCOORDS
+        
+        #wallflags = 0x4|0x20
+        
+        minv = aabb.minv
+        maxv = aabb.maxv
+        size = aabb.size
+        
+        if wallflags & 0x1: # -X
+            self.quadplane(minv, 
+                vec3(0, 0, size.z),
+                vec3(0, size.y, 0), 
+                colors[0], texcoords)
+        if wallflags & 0x2: # -Y
+            self.quadplane(minv, 
+                vec3(size.x, 0, 0),
+                vec3(0, 0, size.z), 
+                colors[1], texcoords)
+        if wallflags & 0x4: # -Z
+            self.quadplane(minv, 
+                vec3(0, size.y, 0), 
+                vec3(size.x, 0, 0),
+                colors[2], texcoords)
+        if wallflags & 0x8: # +X
+            self.quadplane(maxv, 
+                vec3(0, -size.y, 0), 
+                vec3(0, 0, -size.z),
+                colors[3], texcoords)
+        if wallflags & 0x10: # +Y
+            self.quadplane(maxv, 
+                vec3(0, 0, -size.z), 
+                vec3(-size.x, 0, 0),
+                colors[4], texcoords)
+        if wallflags & 0x20: # +Z
+            self.quadplane(maxv, 
+                vec3(-size.x, 0, 0),
+                vec3(0, -size.y, 0), 
+                colors[5], texcoords)
+        
+################################################################################
+
+class FontPainter(PolygonPainter):
+    CONFIG = (
+        'p','c','t', 'n',
+        MeshAttribute(name = 'sharpness', 
+            elements = 1, 
+            location = AttribLocations.in_sharpness
+        )
+    )
+    
+    def __init__(self, **kargs):
+        font = kargs.pop('font', None)
+        PolygonPainter.__init__(self, config=self.CONFIG, **kargs)
+        self.font = font
+               
+    IDENTITY = mat3.identity()
+    
+    def extents(self, text):
+        return self.font.extents(text)
+                
+    def text(self, label, origin, height, color, sharpness=1.1, mtx = None):
+        assert isinstance(origin, vec3)
+        
+        font = self.font
+        font.ensure_loaded()
+                
+        if mtx is None:
+            mtx = self.IDENTITY
+            
+        n = mtx.to_mat3().mul_vec3(vec3(0,0,1))
+        
+        sharp = [sharpness, sharpness, sharpness, sharpness]
+        colors = [color]*4
+        norms = [n]*4
+        
+        for cx,cy,xofs,margins,rect,uvrect in font.iterate_chars(label):
+            if rect is None:
+                continue
+            
+            (x0,y0,x1,y1) = rect
+            (u0,v0,u1,v1) = uvrect
+            
+            self.convex_polygon([origin.add(pt.mul_f(height)) for pt in [
+                mtx.mul_vec3(vec3(x0,y0,0.0)),
+                mtx.mul_vec3(vec3(x1,y0,0.0)),
+                mtx.mul_vec3(vec3(x1,y1,0.0)),
+                mtx.mul_vec3(vec3(x0,y1,0.0)),
+            ]], colors, [
+                vec2(u0,v0),
+                vec2(u1,v0),
+                vec2(u1,v1),
+                vec2(u0,v1),
+            ], norms, sharp)
+        
+################################################################################
+
+def proj_near_far(mtx):
+    p2z = mtx.i22
+    p2w = mtx.i32
+    p3z = mtx.i23
+    p3w = mtx.i33
+    near = (p3w + p2w) / (p3z + p2z)
+    far = (p3w - p2w) / (p3z - p2z)
+    return near, far
+
+################################################################################
+
+class LinePainter(VertexBufferPainter):
+    __slots__ = [
+    ]
+    
+    DRAW_ARRAY_MODE = GL_LINES
+    
+    def __init__(self, **kargs):
+        VertexBufferPainter.__init__(self, name = 'line_painter', 
+            config = 'pc', **kargs)
+        
+    def polyline(self, pts, color, loop = False):
+        """polyline([vec3,...], vec4, bool)"""
+        
+        ptcount = len(pts)
+        offset = 0 if loop else 1 
+        
+        verts = []
+        colors = []
+        
+        for i in range(offset,ptcount):
+            i0 = (i-1)%ptcount
+            verts.append(pts[i0])
+            verts.append(pts[i])
+            colors.append(color)
+            colors.append(color)
+            
+        self.add_vertices(verts, colors)
+        
+    def ray(self, p0, p1, color):
+        #self.cross_vec3(p1, t, color, color, color)
+        td = p1.sub(p0) 
+        t = td.length() / 4.0
+        pz = td.normalize()
+        if pz is None:
+            return
+        px = pz.ortho()
+        py = px.cross(pz)
+        px = px.mul_f(t)
+        py = py.mul_f(t)
+        self.polyline((
+            p0.sub(px), p0.add(px), p1
+        ), color, loop=True)
+        self.polyline((
+            p0.sub(py), p0.add(py), p1
+        ), color, loop=True)
+
+    def normal_vec3(self, pt, norm, color):
+        self.ray(pt, pt.add(norm), color)
+        
+    def plane_vec3(self, pt, norm, r, color):
+        self.normal_vec3(pt, norm.mul_f(r), color)
+        self.plane_disc_vec3(pt, norm, r, color)
+
+    def plane_disc_vec3(self, pt, norm, r, color):
+        b = norm.ortho().normalize()
+        c = norm.cross(b)
+        self.circle(pt, r, color, b, c, 8)        
+
+    def cube(self, p, h2, color):
+        self.aabb(AABB(p.sub_f(h2), p.add_f(h2)), color) 
+
+    def box(self, p, h2, color):
+        self.aabb(AABB(p.sub(h2), p.add(h2)), color) 
+
+    def camera(self, camera, color, use_view=True):
+        cam_attribs = camera.get_view_attrib(0)
+        mtx_cam_p = cam_attribs.projection_matrix
+        mtx_cam_v = cam_attribs.view_matrix
+        if use_view:
+            mtx_cam_inv = mtx_cam_p.mul_mat4(mtx_cam_v).inverse()
+        else:
+            mtx_cam_inv = mtx_cam_p.inverse()
+        near,far = proj_near_far(mtx_cam_p)
+        #self.sphere(camera.world_position, near, color, 6)
+        dist = far - near
+        corners = []
+        for y in (-1,1):
+            for x in (-1,1):
+                r0 = mtx_cam_inv.mul_vec4(vec4(x,y,-1,1))
+                if r0.w:
+                    r0 = r0.div_f(r0.w)
+                r0 = r0.to_vec3()
+                r1 = mtx_cam_inv.mul_vec4(vec4(x,y,1,1))
+                if r1.w:
+                    r1 = r1.div_f(r1.w)
+                r1 = r1.to_vec3()
+                ray = r1.sub(r0)
+                p0 = r0.add(ray.mul_f((near - near) / dist))
+                p1 = r0.add(ray.mul_f((far - near) / dist))
+                for pt in p0,p1:
+                    corners.append(pt)
+        self.frustum(corners, color)
+
+    def frustum(self, points, color):
+        p0,p1,p2,p3,p4,p5,p6,p7 = points
+        self.polyline((
+            p0,p1,p3,p2,
+        ), color, loop = True)
+        self.polyline((
+            p4,p5,p7,p6,
+        ), color, loop = True)
+        self.polyline((
+            p0, p4
+        ), color)
+        self.polyline((
+            p1, p5,
+        ), color)
+        self.polyline((
+            p3, p7,
+        ), color)
+        self.polyline((
+            p2, p6
+        ), color)
+        
+    def aabb(self, aabb, color):
+        self.polyline((
+            vec3(aabb.minv.i0, aabb.minv.i1, aabb.minv.i2),
+            vec3(aabb.maxv.i0, aabb.minv.i1, aabb.minv.i2),
+            vec3(aabb.maxv.i0, aabb.maxv.i1, aabb.minv.i2),
+            vec3(aabb.minv.i0, aabb.maxv.i1, aabb.minv.i2),
+        ), color, loop = True)
+        self.polyline((
+            vec3(aabb.minv.i0, aabb.minv.i1, aabb.maxv.i2),
+            vec3(aabb.maxv.i0, aabb.minv.i1, aabb.maxv.i2),
+            vec3(aabb.maxv.i0, aabb.maxv.i1, aabb.maxv.i2),
+            vec3(aabb.minv.i0, aabb.maxv.i1, aabb.maxv.i2),
+        ), color, loop = True)
+        self.polyline((
+            vec3(aabb.minv.i0, aabb.minv.i1, aabb.minv.i2),
+            vec3(aabb.minv.i0, aabb.minv.i1, aabb.maxv.i2),
+        ), color)
+        self.polyline((
+            vec3(aabb.maxv.i0, aabb.minv.i1, aabb.minv.i2),
+            vec3(aabb.maxv.i0, aabb.minv.i1, aabb.maxv.i2),
+        ), color)
+        self.polyline((
+            vec3(aabb.maxv.i0, aabb.maxv.i1, aabb.minv.i2),
+            vec3(aabb.maxv.i0, aabb.maxv.i1, aabb.maxv.i2),
+        ), color)
+        self.polyline((
+            vec3(aabb.minv.i0, aabb.maxv.i1, aabb.minv.i2),
+            vec3(aabb.minv.i0, aabb.maxv.i1, aabb.maxv.i2),
+        ), color)
+
+    def circle(self, p, r, color, u_axis = X_AXIS, v_axis = Y_AXIS, segments = 16):
+        pts = []
+        for i in xrange(segments):
+            a = (2.0 * math.pi * i) / segments
+            u = math.sin(a) * r
+            v = math.cos(a) * r
+            pts.append(p.add(u_axis.mul_f(u).add(v_axis.mul_f(v))))
+        self.polyline(pts, color, loop = True)
+
+    def circle_x(self, p, r, color, segments = 16):
+        self.circle(p, r, color, Z_AXIS, Y_AXIS, segments)
+        
+    def circle_y(self, p, r, color, segments = 16):
+        self.circle(p, r, color, X_AXIS, Z_AXIS, segments)
+        
+    def circle_z(self, p, r, color, segments = 16):
+        self.circle(p, r, color, X_AXIS, Y_AXIS, segments)
+
+    def sphere(self, p, r, color, segments = 16):
+        self.circle_x(p, r, color, segments)
+        self.circle_y(p, r, color, segments)
+        self.circle_z(p, r, color, segments)
+        
+    def grid_z(self, origin, size, count, color):
+        s2 = size/2.0
+        step = size/count
+        for i in xrange(count+1):
+            n = i*step - s2
+            self.polyline([
+                origin.add(vec3(n, -s2, 0)),
+                origin.add(vec3(n, s2, 0))
+            ], color)
+            self.polyline([
+                origin.add(vec3(-s2, n, 0)),
+                origin.add(vec3(s2, n, 0))
+            ], color)
+        
+    def cross_vec3(self, pt, s, color_x, color_y = None, color_z = None):
+        axis_x = vec3(s, 0.0, 0.0)
+        axis_y = vec3(0.0, s, 0.0)
+        axis_z = vec3(0.0, 0.0, s)
+        
+        if color_y is None:
+            color_y = color_x
+        if color_z is None:
+            color_z = color_x
+        
+        self.polyline([pt.sub(axis_x), pt.add(axis_x)], color_x)
+        self.polyline([pt.sub(axis_y), pt.add(axis_y)], color_y)
+        self.polyline([pt.sub(axis_z), pt.add(axis_z)], color_z)
+        
+    def axis_mat4(self, mtx):
+        origin = mtx.col3()
+        axis_x = origin.add(mtx.col0())
+        axis_y = origin.add(mtx.col1())
+        axis_z = origin.add(mtx.col2())
+        self.polyline([origin,axis_x], vec4(1.0, 0.3, 0.1, 1.0))
+        self.polyline([origin,axis_y], vec4(0.1, 1.0, 0.3, 1.0))
+        self.polyline([origin,axis_z], vec4(0.3, 0.1, 1.0, 1.0))
+
+################################################################################
+
+class DebugGrid(LinePainter):
+    LINE_COLOR = vec4(0.1, 0.2, 0.5, 1.0)
+    
+    def __init__(self):
+        LinePainter.__init__(self, index_limit = 1024)
+        self.name = 'debug_grid'
+        self.material.set_additive()
+        self.update_grid()
+        
+    def update_grid(self):
+        self.clear()
+        self.grid_z(vec3(0,0,0), 100.0, 100, self.LINE_COLOR)
+        self.grid_z(vec3(0,0,0), 10.0, 100, self.LINE_COLOR)
+        self.cross_vec3(vec3(0,0,0), 100.0, 
+            vec4(1.0, 0.0, 0.0, 1.0), 
+            vec4(0.0, 1.0, 0.0, 1.0),
+            vec4(0.0, 0.0, 1.0, 1.0))    

          
A => liminal_legacy/liminal/engine/fileman.py +130 -0
@@ 0,0 1,130 @@ 
+from __future__ import (print_function, division, absolute_import)
+
+import os
+import shutil
+import traceback
+
+from glue import (defproperty, lazydecls, callsetbag, Singleton) 
+from ..utils import DirChangeTracker
+from .config import Config
+
+################################################################################
+
+@lazydecls
+class FileManager(object):
+    __metaclass__ = Singleton
+    
+    basepath = defproperty(readonly = True)
+    change_tracker = defproperty(readonly = True)
+    
+    def __init__(self):
+        self.__basepath = Config().base_path
+        self.__change_tracker = DirChangeTracker(self.__basepath)
+        self.events = self.__change_tracker.events 
+        
+    def abspath(self, path):
+        if not os.path.isabs(path):
+            path = os.path.abspath(os.path.join(self.basepath, path))
+        assert path.startswith(self.basepath)
+        return path
+    
+    def relpath(self, filepath):
+        if os.path.isabs(filepath):
+            filepath = os.path.relpath(filepath, self.basepath)
+        return filepath
+    
+    def isfile(self, filepath):
+        return os.path.isfile(self.abspath(filepath))
+
+    def isdir(self, filepath):
+        return os.path.isdir(self.abspath(filepath))
+
+    def exists(self, filepath):
+        return os.path.exists(self.abspath(filepath))
+    
+    def read(self, obj):
+        filepath = self.abspath(obj)
+        with file(filepath, 'r') as f:
+            data = f.read()
+        return data
+    
+    def write(self, obj, value):
+        try:
+            assert self.isfile(obj)
+            filepath = self.abspath(obj)
+            with file(filepath, 'w') as f:
+                f.write(value)
+        except:
+            traceback.print_exc()
+            return False
+        return True
+    
+    def run(self, obj):