Skip to content

Commit

Permalink
-working SCREEN_TEXTURE, SCREEN_UV shader variables
Browse files Browse the repository at this point in the history
-Added refraction support for default material
-Enabled BCS adjustments, as well as color correction.
  • Loading branch information
reduz committed Jun 6, 2017
1 parent b21e68c commit 0fb9930
Show file tree
Hide file tree
Showing 11 changed files with 234 additions and 82 deletions.
123 changes: 86 additions & 37 deletions drivers/gles3/rasterizer_scene_gles3.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -933,6 +933,15 @@ void RasterizerSceneGLES3::environment_set_tonemap(RID p_env, VS::EnvironmentTon
}

void RasterizerSceneGLES3::environment_set_adjustment(RID p_env, bool p_enable, float p_brightness, float p_contrast, float p_saturation, RID p_ramp) {

Environment *env = environment_owner.getornull(p_env);
ERR_FAIL_COND(!env);

env->adjustments_enabled = p_enable;
env->adjustments_brightness = p_brightness;
env->adjustments_contrast = p_contrast;
env->adjustments_saturation = p_saturation;
env->color_correction = p_ramp;
}

RID RasterizerSceneGLES3::light_instance_create(RID p_light) {
Expand Down Expand Up @@ -1697,7 +1706,7 @@ void RasterizerSceneGLES3::_setup_light(RenderList::Element *e, const Transform

GIProbeInstance *gipi = gi_probe_instance_owner.getptr(ridp[0]);

glActiveTexture(GL_TEXTURE0 + storage->config.max_texture_image_units - 10);
glActiveTexture(GL_TEXTURE0 + storage->config.max_texture_image_units - 9);
glBindTexture(GL_TEXTURE_3D, gipi->tex_cache);
state.scene_shader.set_uniform(SceneShaderGLES3::GI_PROBE_XFORM1, gipi->transform_to_data * p_view_transform);
state.scene_shader.set_uniform(SceneShaderGLES3::GI_PROBE_BOUNDS1, gipi->bounds);
Expand All @@ -1709,7 +1718,7 @@ void RasterizerSceneGLES3::_setup_light(RenderList::Element *e, const Transform

GIProbeInstance *gipi2 = gi_probe_instance_owner.getptr(ridp[1]);

glActiveTexture(GL_TEXTURE0 + storage->config.max_texture_image_units - 11);
glActiveTexture(GL_TEXTURE0 + storage->config.max_texture_image_units - 10);
glBindTexture(GL_TEXTURE_3D, gipi2->tex_cache);
state.scene_shader.set_uniform(SceneShaderGLES3::GI_PROBE_XFORM2, gipi2->transform_to_data * p_view_transform);
state.scene_shader.set_uniform(SceneShaderGLES3::GI_PROBE_BOUNDS2, gipi2->bounds);
Expand Down Expand Up @@ -1751,8 +1760,6 @@ void RasterizerSceneGLES3::_render_list(RenderList::Element **p_elements, int p_

if (!p_shadow && !p_directional_add) {
glBindBufferBase(GL_UNIFORM_BUFFER, 2, state.env_radiance_ubo); //bind environment radiance info
glActiveTexture(GL_TEXTURE0 + storage->config.max_texture_image_units - 1);
glBindTexture(GL_TEXTURE_2D, state.brdf_texture);

if (p_base_env) {
glActiveTexture(GL_TEXTURE0 + storage->config.max_texture_image_units - 2);
Expand Down Expand Up @@ -1934,7 +1941,7 @@ void RasterizerSceneGLES3::_render_list(RenderList::Element **p_elements, int p_

if (skeleton.is_valid()) {
RasterizerStorageGLES3::Skeleton *sk = storage->skeleton_owner.getornull(skeleton);
glActiveTexture(GL_TEXTURE0 + storage->config.max_texture_image_units - 6);
glActiveTexture(GL_TEXTURE0 + storage->config.max_texture_image_units - 1);
glBindTexture(GL_TEXTURE_2D, sk->texture);
}
}
Expand Down Expand Up @@ -2023,7 +2030,7 @@ void RasterizerSceneGLES3::_add_geometry(RasterizerStorageGLES3::Geometry *p_geo

ERR_FAIL_COND(!m);

bool has_base_alpha = (m->shader->spatial.uses_alpha);
bool has_base_alpha = (m->shader->spatial.uses_alpha || m->shader->spatial.uses_screen_texture);
bool has_blend_alpha = m->shader->spatial.blend_mode != RasterizerStorageGLES3::Shader::Spatial::BLEND_MODE_MIX || m->shader->spatial.ontop;
bool has_alpha = has_base_alpha || has_blend_alpha;
bool shadow = false;
Expand All @@ -2038,6 +2045,10 @@ void RasterizerSceneGLES3::_add_geometry(RasterizerStorageGLES3::Geometry *p_geo
state.used_sss = true;
}

if (m->shader->spatial.uses_screen_texture) {
state.used_screen_texture = true;
}

if (p_shadow) {

if (has_blend_alpha || (has_base_alpha && m->shader->spatial.depth_draw_mode != RasterizerStorageGLES3::Shader::Spatial::DEPTH_DRAW_ALPHA_PREPASS))
Expand Down Expand Up @@ -2797,6 +2808,7 @@ void RasterizerSceneGLES3::_fill_render_list(InstanceBase **p_cull_result, int p
current_geometry_index = 0;
current_material_index = 0;
state.used_sss = false;
state.used_screen_texture = false;

//fill list

Expand Down Expand Up @@ -2874,6 +2886,39 @@ void RasterizerSceneGLES3::_fill_render_list(InstanceBase **p_cull_result, int p
}
}

void RasterizerSceneGLES3::_blur_effect_buffer() {

//blur diffuse into effect mipmaps using separatable convolution
//storage->shaders.copy.set_conditional(CopyShaderGLES3::GAUSSIAN_HORIZONTAL,true);
for (int i = 0; i < storage->frame.current_rt->effects.mip_maps[1].sizes.size(); i++) {

int vp_w = storage->frame.current_rt->effects.mip_maps[1].sizes[i].width;
int vp_h = storage->frame.current_rt->effects.mip_maps[1].sizes[i].height;
glViewport(0, 0, vp_w, vp_h);
//horizontal pass
state.effect_blur_shader.set_conditional(EffectBlurShaderGLES3::GAUSSIAN_HORIZONTAL, true);
state.effect_blur_shader.bind();
state.effect_blur_shader.set_uniform(EffectBlurShaderGLES3::PIXEL_SIZE, Vector2(1.0 / vp_w, 1.0 / vp_h));
state.effect_blur_shader.set_uniform(EffectBlurShaderGLES3::LOD, float(i));
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, storage->frame.current_rt->effects.mip_maps[0].color); //previous level, since mipmaps[0] starts one level bigger
glBindFramebuffer(GL_FRAMEBUFFER, storage->frame.current_rt->effects.mip_maps[1].sizes[i].fbo);
_copy_screen();
state.effect_blur_shader.set_conditional(EffectBlurShaderGLES3::GAUSSIAN_HORIZONTAL, false);

//vertical pass
state.effect_blur_shader.set_conditional(EffectBlurShaderGLES3::GAUSSIAN_VERTICAL, true);
state.effect_blur_shader.bind();
state.effect_blur_shader.set_uniform(EffectBlurShaderGLES3::PIXEL_SIZE, Vector2(1.0 / vp_w, 1.0 / vp_h));
state.effect_blur_shader.set_uniform(EffectBlurShaderGLES3::LOD, float(i));
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, storage->frame.current_rt->effects.mip_maps[1].color);
glBindFramebuffer(GL_FRAMEBUFFER, storage->frame.current_rt->effects.mip_maps[0].sizes[i + 1].fbo); //next level, since mipmaps[0] starts one level bigger
_copy_screen();
state.effect_blur_shader.set_conditional(EffectBlurShaderGLES3::GAUSSIAN_VERTICAL, false);
}
}

void RasterizerSceneGLES3::_render_mrts(Environment *env, const CameraMatrix &p_cam_projection) {

glDepthMask(GL_FALSE);
Expand Down Expand Up @@ -3080,33 +3125,7 @@ void RasterizerSceneGLES3::_render_mrts(Environment *env, const CameraMatrix &p_

//blur diffuse into effect mipmaps using separatable convolution
//storage->shaders.copy.set_conditional(CopyShaderGLES3::GAUSSIAN_HORIZONTAL,true);
for (int i = 0; i < storage->frame.current_rt->effects.mip_maps[1].sizes.size(); i++) {

int vp_w = storage->frame.current_rt->effects.mip_maps[1].sizes[i].width;
int vp_h = storage->frame.current_rt->effects.mip_maps[1].sizes[i].height;
glViewport(0, 0, vp_w, vp_h);
//horizontal pass
state.effect_blur_shader.set_conditional(EffectBlurShaderGLES3::GAUSSIAN_HORIZONTAL, true);
state.effect_blur_shader.bind();
state.effect_blur_shader.set_uniform(EffectBlurShaderGLES3::PIXEL_SIZE, Vector2(1.0 / vp_w, 1.0 / vp_h));
state.effect_blur_shader.set_uniform(EffectBlurShaderGLES3::LOD, float(i));
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, storage->frame.current_rt->effects.mip_maps[0].color); //previous level, since mipmaps[0] starts one level bigger
glBindFramebuffer(GL_FRAMEBUFFER, storage->frame.current_rt->effects.mip_maps[1].sizes[i].fbo);
_copy_screen();
state.effect_blur_shader.set_conditional(EffectBlurShaderGLES3::GAUSSIAN_HORIZONTAL, false);

//vertical pass
state.effect_blur_shader.set_conditional(EffectBlurShaderGLES3::GAUSSIAN_VERTICAL, true);
state.effect_blur_shader.bind();
state.effect_blur_shader.set_uniform(EffectBlurShaderGLES3::PIXEL_SIZE, Vector2(1.0 / vp_w, 1.0 / vp_h));
state.effect_blur_shader.set_uniform(EffectBlurShaderGLES3::LOD, float(i));
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, storage->frame.current_rt->effects.mip_maps[1].color);
glBindFramebuffer(GL_FRAMEBUFFER, storage->frame.current_rt->effects.mip_maps[0].sizes[i + 1].fbo); //next level, since mipmaps[0] starts one level bigger
_copy_screen();
state.effect_blur_shader.set_conditional(EffectBlurShaderGLES3::GAUSSIAN_VERTICAL, false);
}
_blur_effect_buffer();

//perform SSR

Expand Down Expand Up @@ -3177,6 +3196,13 @@ void RasterizerSceneGLES3::_render_mrts(Environment *env, const CameraMatrix &p_

glDisable(GL_BLEND); //end additive

if (state.used_screen_texture) {
_blur_effect_buffer();
//restored framebuffer
glBindFramebuffer(GL_FRAMEBUFFER, storage->frame.current_rt->effects.mip_maps[0].sizes[0].fbo);
glViewport(0, 0, storage->frame.current_rt->width, storage->frame.current_rt->height);
}

state.effect_blur_shader.set_conditional(EffectBlurShaderGLES3::SIMPLE_COPY, true);
state.effect_blur_shader.bind();
state.effect_blur_shader.set_uniform(EffectBlurShaderGLES3::LOD, float(0));
Expand Down Expand Up @@ -3594,6 +3620,17 @@ void RasterizerSceneGLES3::_post_process(Environment *env, const CameraMatrix &p
glBindTexture(GL_TEXTURE_2D, storage->frame.current_rt->effects.mip_maps[0].color);
}

if (env->adjustments_enabled) {

state.tonemap_shader.set_conditional(TonemapShaderGLES3::USE_BCS, true);
RasterizerStorageGLES3::Texture *tex = storage->texture_owner.getornull(env->color_correction);
if (tex) {
state.tonemap_shader.set_conditional(TonemapShaderGLES3::USE_COLOR_CORRECTION, true);
glActiveTexture(GL_TEXTURE3);
glBindTexture(tex->target, tex->tex_id);
}
}

state.tonemap_shader.bind();

state.tonemap_shader.set_uniform(TonemapShaderGLES3::EXPOSURE, env->tone_mapper_exposure);
Expand All @@ -3616,6 +3653,11 @@ void RasterizerSceneGLES3::_post_process(Environment *env, const CameraMatrix &p
state.tonemap_shader.set_uniform(TonemapShaderGLES3::AUTO_EXPOSURE_GREY, env->auto_exposure_grey);
}

if (env->adjustments_enabled) {

state.tonemap_shader.set_uniform(TonemapShaderGLES3::BCS, Vector3(env->adjustments_brightness, env->adjustments_contrast, env->adjustments_saturation));
}

_copy_screen();

//turn off everything used
Expand All @@ -3634,6 +3676,8 @@ void RasterizerSceneGLES3::_post_process(Environment *env, const CameraMatrix &p
state.tonemap_shader.set_conditional(TonemapShaderGLES3::USE_GLOW_SCREEN, false);
state.tonemap_shader.set_conditional(TonemapShaderGLES3::USE_GLOW_SOFTLIGHT, false);
state.tonemap_shader.set_conditional(TonemapShaderGLES3::USE_GLOW_FILTER_BICUBIC, false);
state.tonemap_shader.set_conditional(TonemapShaderGLES3::USE_BCS, false);
state.tonemap_shader.set_conditional(TonemapShaderGLES3::USE_COLOR_CORRECTION, false);
}

void RasterizerSceneGLES3::render_scene(const Transform &p_cam_transform, const CameraMatrix &p_cam_projection, bool p_cam_ortogonal, InstanceBase **p_cull_result, int p_cull_count, RID *p_light_cull_result, int p_light_cull_count, RID *p_reflection_probe_cull_result, int p_reflection_probe_cull_count, RID p_environment, RID p_shadow_atlas, RID p_reflection_atlas, RID p_reflection_probe, int p_reflection_probe_pass) {
Expand All @@ -3648,7 +3692,7 @@ void RasterizerSceneGLES3::render_scene(const Transform &p_cam_transform, const
ReflectionAtlas *reflection_atlas = reflection_atlas_owner.getornull(p_reflection_atlas);

if (shadow_atlas && shadow_atlas->size) {
glActiveTexture(GL_TEXTURE0 + storage->config.max_texture_image_units - 3);
glActiveTexture(GL_TEXTURE0 + storage->config.max_texture_image_units - 5);
glBindTexture(GL_TEXTURE_2D, shadow_atlas->depth);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC, GL_LESS);
Expand All @@ -3657,7 +3701,7 @@ void RasterizerSceneGLES3::render_scene(const Transform &p_cam_transform, const
}

if (reflection_atlas && reflection_atlas->size) {
glActiveTexture(GL_TEXTURE0 + storage->config.max_texture_image_units - 5);
glActiveTexture(GL_TEXTURE0 + storage->config.max_texture_image_units - 3);
glBindTexture(GL_TEXTURE_2D, reflection_atlas->color);
}

Expand Down Expand Up @@ -3721,7 +3765,7 @@ void RasterizerSceneGLES3::render_scene(const Transform &p_cam_transform, const
glBindFramebuffer(GL_READ_FRAMEBUFFER, 0);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
//bind depth for read
glActiveTexture(GL_TEXTURE0 + storage->config.max_texture_image_units - 9);
glActiveTexture(GL_TEXTURE0 + storage->config.max_texture_image_units - 8);
glBindTexture(GL_TEXTURE_2D, storage->frame.current_rt->depth);
}

Expand Down Expand Up @@ -3774,7 +3818,7 @@ void RasterizerSceneGLES3::render_scene(const Transform &p_cam_transform, const

} else {

use_mrt = env && (state.used_sss || env->ssao_enabled || env->ssr_enabled); //only enable MRT rendering if any of these is enabled
use_mrt = env && (state.used_screen_texture || state.used_sss || env->ssao_enabled || env->ssr_enabled); //only enable MRT rendering if any of these is enabled

glViewport(0, 0, storage->frame.current_rt->width, storage->frame.current_rt->height);

Expand Down Expand Up @@ -3910,6 +3954,11 @@ void RasterizerSceneGLES3::render_scene(const Transform &p_cam_transform, const
_render_mrts(env, p_cam_projection);
}

if (state.used_screen_texture) {
glActiveTexture(GL_TEXTURE0 + storage->config.max_texture_image_units - 7);
glBindTexture(GL_TEXTURE_2D, storage->frame.current_rt->effects.mip_maps[0].color);
}

glEnable(GL_BLEND);
glDepthMask(GL_TRUE);
glEnable(GL_DEPTH_TEST);
Expand Down
13 changes: 13 additions & 0 deletions drivers/gles3/rasterizer_scene_gles3.h
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@ class RasterizerSceneGLES3 : public RasterizerScene {

bool cull_front;
bool used_sss;
bool used_screen_texture;

} state;

Expand Down Expand Up @@ -389,6 +390,12 @@ class RasterizerSceneGLES3 : public RasterizerScene {
float dof_blur_near_amount;
VS::EnvironmentDOFBlurQuality dof_blur_near_quality;

bool adjustments_enabled;
float adjustments_brightness;
float adjustments_contrast;
float adjustments_saturation;
RID color_correction;

Environment() {
bg_mode = VS::ENV_BG_CLEAR_COLOR;
sky_scale = 1.0;
Expand Down Expand Up @@ -445,6 +452,11 @@ class RasterizerSceneGLES3 : public RasterizerScene {
dof_blur_near_transition = 1;
dof_blur_near_amount = 0.1;
dof_blur_near_quality = VS::ENV_DOF_BLUR_QUALITY_MEDIUM;

adjustments_enabled = false;
adjustments_brightness = 1.0;
adjustments_contrast = 1.0;
adjustments_saturation = 1.0;
}
};

Expand Down Expand Up @@ -711,6 +723,7 @@ class RasterizerSceneGLES3 : public RasterizerScene {

void _fill_render_list(InstanceBase **p_cull_result, int p_cull_count, bool p_shadow);

void _blur_effect_buffer();
void _render_mrts(Environment *env, const CameraMatrix &p_cam_projection);
void _post_process(Environment *env, const CameraMatrix &p_cam_projection);

Expand Down
6 changes: 4 additions & 2 deletions drivers/gles3/rasterizer_storage_gles3.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1463,6 +1463,7 @@ void RasterizerStorageGLES3::_update_shader(Shader *p_shader) const {
p_shader->spatial.unshaded = false;
p_shader->spatial.ontop = false;
p_shader->spatial.uses_sss = false;
p_shader->spatial.uses_screen_texture = false;
p_shader->spatial.uses_vertex = false;
p_shader->spatial.writes_modelview_or_projection = false;

Expand All @@ -1488,6 +1489,7 @@ void RasterizerStorageGLES3::_update_shader(Shader *p_shader) const {

shaders.actions_scene.usage_flag_pointers["SSS_STRENGTH"] = &p_shader->spatial.uses_sss;
shaders.actions_scene.usage_flag_pointers["DISCARD"] = &p_shader->spatial.uses_discard;
shaders.actions_scene.usage_flag_pointers["SCREEN_TEXTURE"] = &p_shader->spatial.uses_screen_texture;

shaders.actions_scene.write_flag_pointers["MODELVIEW_MATRIX"] = &p_shader->spatial.writes_modelview_or_projection;
shaders.actions_scene.write_flag_pointers["PROJECTION_MATRIX"] = &p_shader->spatial.writes_modelview_or_projection;
Expand Down Expand Up @@ -5669,8 +5671,8 @@ void RasterizerStorageGLES3::_render_target_allocate(RenderTarget *rt) {

glGenTextures(1, &rt->buffers.effect);
glBindTexture(GL_TEXTURE_2D, rt->buffers.effect);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, rt->width, rt->height, 0,
GL_RGBA, GL_UNSIGNED_BYTE, NULL);
glTexImage2D(GL_TEXTURE_2D, 0, color_internal_format, rt->width, rt->height, 0,
color_format, color_type, NULL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
Expand Down
1 change: 1 addition & 0 deletions drivers/gles3/rasterizer_storage_gles3.h
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,7 @@ class RasterizerStorageGLES3 : public RasterizerStorage {
bool uses_vertex;
bool uses_discard;
bool uses_sss;
bool uses_screen_texture;
bool writes_modelview_or_projection;

} spatial;
Expand Down
5 changes: 5 additions & 0 deletions drivers/gles3/shader_compiler_gles3.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -745,6 +745,9 @@ ShaderCompilerGLES3::ShaderCompilerGLES3() {
//actions[VS::SHADER_SPATIAL].renames["SCREEN_UV"]=ShaderLanguage::TYPE_VEC2;
actions[VS::SHADER_SPATIAL].renames["POINT_COORD"] = "gl_PointCoord";
actions[VS::SHADER_SPATIAL].renames["INSTANCE_CUSTOM"] = "instance_custom";
actions[VS::SHADER_SPATIAL].renames["SCREEN_UV"] = "screen_uv";
actions[VS::SHADER_SPATIAL].renames["SCREEN_TEXTURE"] = "screen_texture";
actions[VS::SHADER_SPATIAL].renames["SIDE"] = "side";

actions[VS::SHADER_SPATIAL].usage_defines["TANGENT"] = "#define ENABLE_TANGENT_INTERP\n";
actions[VS::SHADER_SPATIAL].usage_defines["BINORMAL"] = "@TANGENT";
Expand All @@ -763,6 +766,8 @@ ShaderCompilerGLES3::ShaderCompilerGLES3() {
actions[VS::SHADER_SPATIAL].usage_defines["INSTANCE_CUSTOM"] = "#define ENABLE_INSTANCE_CUSTOM\n";

actions[VS::SHADER_SPATIAL].usage_defines["SSS_STRENGTH"] = "#define ENABLE_SSS\n";
actions[VS::SHADER_SPATIAL].usage_defines["SCREEN_TEXTURE"] = "#define SCREEN_TEXTURE_USED\n";
actions[VS::SHADER_SPATIAL].usage_defines["SCREEN_UV"] = "#define SCREEN_UV_USED\n";

actions[VS::SHADER_SPATIAL].renames["SSS_STRENGTH"] = "sss_strength";

Expand Down
31 changes: 31 additions & 0 deletions drivers/gles3/shaders/copy.glsl
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,24 @@ uniform vec2 pixel_size;

in vec2 uv2_interp;


#ifdef USE_BCS

uniform vec3 bcs;

#endif

#ifdef USE_COLOR_CORRECTION

uniform sampler2D color_correction; //texunit:1

#endif

layout(location = 0) out vec4 frag_color;




void main() {

//vec4 color = color_interp;
Expand Down Expand Up @@ -135,6 +151,21 @@ void main() {
color+=texture( source, uv_interp+vec2( 0.0,-2.0)*pixel_size )*0.06136;
#endif

#ifdef USE_BCS

color.rgb = mix(vec3(0.0),color.rgb,bcs.x);
color.rgb = mix(vec3(0.5),color.rgb,bcs.y);
color.rgb = mix(vec3(dot(vec3(1.0),color.rgb)*0.33333),color.rgb,bcs.z);

#endif

#ifdef USE_COLOR_CORRECTION

color.r = texture(color_correction,vec2(color.r,0.0)).r;
color.g = texture(color_correction,vec2(color.g,0.0)).g;
color.b = texture(color_correction,vec2(color.b,0.0)).b;
#endif

#ifdef USE_MULTIPLIER
color.rgb*=multiplier;
#endif
Expand Down
Loading

1 comment on commit 0fb9930

@toger5
Copy link
Contributor

@toger5 toger5 commented on 0fb9930 Jun 6, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AMAZING!

Please sign in to comment.