diff --git a/assets/voxygen/shaders/antialias/fxaa.glsl b/assets/voxygen/shaders/antialias/fxaa.glsl new file mode 100644 index 0000000000..700dfa4e90 --- /dev/null +++ b/assets/voxygen/shaders/antialias/fxaa.glsl @@ -0,0 +1,137 @@ +uniform sampler2D src_color; + +const float FXAA_SCALE = 1.5; + +/** +Basic FXAA implementation based on the code on geeks3d.com with the +modification that the texture2DLod stuff was removed since it's +unsupported by WebGL. + +-- + +From: +https://github.com/mitsuhiko/webgl-meincraft + +Copyright (c) 2011 by Armin Ronacher. + +Some rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + + * The names of the contributors may not be used to endorse or + promote products derived from this software without specific + prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#ifndef FXAA_REDUCE_MIN + #define FXAA_REDUCE_MIN (1.0/ 128.0) +#endif +#ifndef FXAA_REDUCE_MUL + #define FXAA_REDUCE_MUL (1.0 / 8.0) +#endif +#ifndef FXAA_SPAN_MAX + #define FXAA_SPAN_MAX 8.0 +#endif + +//optimized version for mobile, where dependent +//texture reads can be a bottleneck +vec4 fxaa(sampler2D tex, vec2 fragCoord, vec2 resolution, + vec2 v_rgbNW, vec2 v_rgbNE, + vec2 v_rgbSW, vec2 v_rgbSE, + vec2 v_rgbM) { + vec4 color; + mediump vec2 inverseVP = vec2(1.0 / resolution.x, 1.0 / resolution.y); + vec3 rgbNW = texture(tex, v_rgbNW).xyz; + vec3 rgbNE = texture(tex, v_rgbNE).xyz; + vec3 rgbSW = texture(tex, v_rgbSW).xyz; + vec3 rgbSE = texture(tex, v_rgbSE).xyz; + vec4 texColor = texture(tex, v_rgbM); + vec3 rgbM = texColor.xyz; + vec3 luma = vec3(0.299, 0.587, 0.114); + float lumaNW = dot(rgbNW, luma); + float lumaNE = dot(rgbNE, luma); + float lumaSW = dot(rgbSW, luma); + float lumaSE = dot(rgbSE, luma); + float lumaM = dot(rgbM, luma); + float lumaMin = min(lumaM, min(min(lumaNW, lumaNE), min(lumaSW, lumaSE))); + float lumaMax = max(lumaM, max(max(lumaNW, lumaNE), max(lumaSW, lumaSE))); + + mediump vec2 dir; + dir.x = -((lumaNW + lumaNE) - (lumaSW + lumaSE)); + dir.y = ((lumaNW + lumaSW) - (lumaNE + lumaSE)); + + float dirReduce = max((lumaNW + lumaNE + lumaSW + lumaSE) * + (0.25 * FXAA_REDUCE_MUL), FXAA_REDUCE_MIN); + + float rcpDirMin = 1.0 / (min(abs(dir.x), abs(dir.y)) + dirReduce); + dir = min(vec2(FXAA_SPAN_MAX, FXAA_SPAN_MAX), + max(vec2(-FXAA_SPAN_MAX, -FXAA_SPAN_MAX), + dir * rcpDirMin)) * inverseVP; + + vec3 rgbA = 0.5 * ( + texture(tex, fragCoord * inverseVP + dir * (1.0 / 3.0 - 0.5)).xyz + + texture(tex, fragCoord * inverseVP + dir * (2.0 / 3.0 - 0.5)).xyz); + vec3 rgbB = rgbA * 0.5 + 0.25 * ( + texture(tex, fragCoord * inverseVP + dir * -0.5).xyz + + texture(tex, fragCoord * inverseVP + dir * 0.5).xyz); + + float lumaB = dot(rgbB, luma); + if ((lumaB < lumaMin) || (lumaB > lumaMax)) + color = vec4(rgbA, texColor.a); + else + color = vec4(rgbB, texColor.a); + return color; +} + + +void texcoords(vec2 fragCoord, vec2 resolution, + out vec2 v_rgbNW, out vec2 v_rgbNE, + out vec2 v_rgbSW, out vec2 v_rgbSE, + out vec2 v_rgbM) { + vec2 inverseVP = 1.0 / resolution.xy; + v_rgbNW = (fragCoord + vec2(-1.0, -1.0)) * inverseVP; + v_rgbNE = (fragCoord + vec2(1.0, -1.0)) * inverseVP; + v_rgbSW = (fragCoord + vec2(-1.0, 1.0)) * inverseVP; + v_rgbSE = (fragCoord + vec2(1.0, 1.0)) * inverseVP; + v_rgbM = vec2(fragCoord * inverseVP); +} + + +vec4 aa_apply(sampler2D tex, vec2 fragCoord, vec2 resolution) { + mediump vec2 v_rgbNW; + mediump vec2 v_rgbNE; + mediump vec2 v_rgbSW; + mediump vec2 v_rgbSE; + mediump vec2 v_rgbM; + + vec2 scaled_fc = fragCoord * FXAA_SCALE; + vec2 scaled_res = resolution * FXAA_SCALE; + + //compute the texture coords + texcoords(scaled_fc, scaled_res, v_rgbNW, v_rgbNE, v_rgbSW, v_rgbSE, v_rgbM); + + //compute FXAA + return fxaa(tex, scaled_fc, scaled_res, v_rgbNW, v_rgbNE, v_rgbSW, v_rgbSE, v_rgbM); +} \ No newline at end of file diff --git a/assets/voxygen/shaders/antialias/msaa-x16.glsl b/assets/voxygen/shaders/antialias/msaa-x16.glsl new file mode 100644 index 0000000000..c9fb6514b7 --- /dev/null +++ b/assets/voxygen/shaders/antialias/msaa-x16.glsl @@ -0,0 +1,30 @@ +uniform sampler2DMS src_color; + +vec4 aa_apply(sampler2DMS tex, vec2 fragCoord, vec2 resolution) { + ivec2 texel_coord = ivec2(fragCoord.x, fragCoord.y); + + vec4 sample1 = texelFetch(tex, texel_coord, 0); + vec4 sample2 = texelFetch(tex, texel_coord, 1); + vec4 sample3 = texelFetch(tex, texel_coord, 2); + vec4 sample4 = texelFetch(tex, texel_coord, 3); + vec4 sample5 = texelFetch(tex, texel_coord, 4); + vec4 sample6 = texelFetch(tex, texel_coord, 5); + vec4 sample7 = texelFetch(tex, texel_coord, 6); + vec4 sample8 = texelFetch(tex, texel_coord, 7); + vec4 sample9 = texelFetch(tex, texel_coord, 8); + vec4 sample10 = texelFetch(tex, texel_coord, 9); + vec4 sample11 = texelFetch(tex, texel_coord, 11); + vec4 sample12 = texelFetch(tex, texel_coord, 12); + vec4 sample13 = texelFetch(tex, texel_coord, 13); + vec4 sample14 = texelFetch(tex, texel_coord, 14); + vec4 sample15 = texelFetch(tex, texel_coord, 15); + vec4 sample16 = texelFetch(tex, texel_coord, 16); + + // Average Samples + vec4 msaa_color = ( + sample1 + sample2 + sample3 + sample4 + sample5 + sample6 + sample7 + sample8 + + sample9 + sample10 + sample11 + sample12 + sample13 + sample14 + sample15 + sample16 + ) / 16.0; + + return msaa_color; +} \ No newline at end of file diff --git a/assets/voxygen/shaders/antialias/msaa-x4.glsl b/assets/voxygen/shaders/antialias/msaa-x4.glsl new file mode 100644 index 0000000000..287c701acd --- /dev/null +++ b/assets/voxygen/shaders/antialias/msaa-x4.glsl @@ -0,0 +1,15 @@ +uniform sampler2DMS src_color; + +vec4 aa_apply(sampler2DMS tex, vec2 fragCoord, vec2 resolution) { + ivec2 texel_coord = ivec2(fragCoord.x, fragCoord.y); + + vec4 sample1 = texelFetch(tex, texel_coord, 0); + vec4 sample2 = texelFetch(tex, texel_coord, 1); + vec4 sample3 = texelFetch(tex, texel_coord, 2); + vec4 sample4 = texelFetch(tex, texel_coord, 3); + + // Average Samples + vec4 msaa_color = (sample1 + sample2 + sample3 + sample4) / 4.0; + + return msaa_color; +} \ No newline at end of file diff --git a/assets/voxygen/shaders/antialias/msaa-x8.glsl b/assets/voxygen/shaders/antialias/msaa-x8.glsl new file mode 100644 index 0000000000..9a8652cf81 --- /dev/null +++ b/assets/voxygen/shaders/antialias/msaa-x8.glsl @@ -0,0 +1,19 @@ +uniform sampler2DMS src_color; + +vec4 aa_apply(sampler2DMS tex, vec2 fragCoord, vec2 resolution) { + ivec2 texel_coord = ivec2(fragCoord.x, fragCoord.y); + + vec4 sample1 = texelFetch(tex, texel_coord, 0); + vec4 sample2 = texelFetch(tex, texel_coord, 1); + vec4 sample3 = texelFetch(tex, texel_coord, 2); + vec4 sample4 = texelFetch(tex, texel_coord, 3); + vec4 sample5 = texelFetch(tex, texel_coord, 4); + vec4 sample6 = texelFetch(tex, texel_coord, 5); + vec4 sample7 = texelFetch(tex, texel_coord, 6); + vec4 sample8 = texelFetch(tex, texel_coord, 7); + + // Average Samples + vec4 msaa_color = (sample1 + sample2 + sample3 + sample4 + sample5 + sample6 + sample7 + sample8) / 8.0; + + return msaa_color; +} \ No newline at end of file diff --git a/assets/voxygen/shaders/antialias/none.glsl b/assets/voxygen/shaders/antialias/none.glsl new file mode 100644 index 0000000000..d6230fa618 --- /dev/null +++ b/assets/voxygen/shaders/antialias/none.glsl @@ -0,0 +1,5 @@ +uniform sampler2D src_color; + +vec4 aa_apply(sampler2D tex, vec2 fragCoord, vec2 resolution) { + return texture(src_color, fragCoord / resolution); +} \ No newline at end of file diff --git a/assets/voxygen/shaders/postprocess-frag.glsl b/assets/voxygen/shaders/postprocess-frag.glsl index fe1aa3cf81..4193bda525 100644 --- a/assets/voxygen/shaders/postprocess-frag.glsl +++ b/assets/voxygen/shaders/postprocess-frag.glsl @@ -1,8 +1,8 @@ #version 330 core #include - -uniform sampler2DMS src_color; +// Note: The sampler uniform is declared here because it differs for MSAA +#include in vec2 f_pos; @@ -13,137 +13,6 @@ uniform u_locals { out vec4 tgt_color; -/** -Basic FXAA implementation based on the code on geeks3d.com with the -modification that the texture2DLod stuff was removed since it's -unsupported by WebGL. - --- - -From: -https://github.com/mitsuhiko/webgl-meincraft - -Copyright (c) 2011 by Armin Ronacher. - -Some rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - - * Redistributions in binary form must reproduce the above - copyright notice, this list of conditions and the following - disclaimer in the documentation and/or other materials provided - with the distribution. - - * The names of the contributors may not be used to endorse or - promote products derived from this software without specific - prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ - -#ifndef FXAA_REDUCE_MIN - #define FXAA_REDUCE_MIN (1.0/ 128.0) -#endif -#ifndef FXAA_REDUCE_MUL - #define FXAA_REDUCE_MUL (1.0 / 8.0) -#endif -#ifndef FXAA_SPAN_MAX - #define FXAA_SPAN_MAX 8.0 -#endif - -//optimized version for mobile, where dependent -//texture reads can be a bottleneck -vec4 fxaa(sampler2D tex, vec2 fragCoord, vec2 resolution, - vec2 v_rgbNW, vec2 v_rgbNE, - vec2 v_rgbSW, vec2 v_rgbSE, - vec2 v_rgbM) { - vec4 color; - mediump vec2 inverseVP = vec2(1.0 / resolution.x, 1.0 / resolution.y); - vec3 rgbNW = texture(tex, v_rgbNW).xyz; - vec3 rgbNE = texture(tex, v_rgbNE).xyz; - vec3 rgbSW = texture(tex, v_rgbSW).xyz; - vec3 rgbSE = texture(tex, v_rgbSE).xyz; - vec4 texColor = texture(tex, v_rgbM); - vec3 rgbM = texColor.xyz; - vec3 luma = vec3(0.299, 0.587, 0.114); - float lumaNW = dot(rgbNW, luma); - float lumaNE = dot(rgbNE, luma); - float lumaSW = dot(rgbSW, luma); - float lumaSE = dot(rgbSE, luma); - float lumaM = dot(rgbM, luma); - float lumaMin = min(lumaM, min(min(lumaNW, lumaNE), min(lumaSW, lumaSE))); - float lumaMax = max(lumaM, max(max(lumaNW, lumaNE), max(lumaSW, lumaSE))); - - mediump vec2 dir; - dir.x = -((lumaNW + lumaNE) - (lumaSW + lumaSE)); - dir.y = ((lumaNW + lumaSW) - (lumaNE + lumaSE)); - - float dirReduce = max((lumaNW + lumaNE + lumaSW + lumaSE) * - (0.25 * FXAA_REDUCE_MUL), FXAA_REDUCE_MIN); - - float rcpDirMin = 1.0 / (min(abs(dir.x), abs(dir.y)) + dirReduce); - dir = min(vec2(FXAA_SPAN_MAX, FXAA_SPAN_MAX), - max(vec2(-FXAA_SPAN_MAX, -FXAA_SPAN_MAX), - dir * rcpDirMin)) * inverseVP; - - vec3 rgbA = 0.5 * ( - texture(tex, fragCoord * inverseVP + dir * (1.0 / 3.0 - 0.5)).xyz + - texture(tex, fragCoord * inverseVP + dir * (2.0 / 3.0 - 0.5)).xyz); - vec3 rgbB = rgbA * 0.5 + 0.25 * ( - texture(tex, fragCoord * inverseVP + dir * -0.5).xyz + - texture(tex, fragCoord * inverseVP + dir * 0.5).xyz); - - float lumaB = dot(rgbB, luma); - if ((lumaB < lumaMin) || (lumaB > lumaMax)) - color = vec4(rgbA, texColor.a); - else - color = vec4(rgbB, texColor.a); - return color; -} - - -void texcoords(vec2 fragCoord, vec2 resolution, - out vec2 v_rgbNW, out vec2 v_rgbNE, - out vec2 v_rgbSW, out vec2 v_rgbSE, - out vec2 v_rgbM) { - vec2 inverseVP = 1.0 / resolution.xy; - v_rgbNW = (fragCoord + vec2(-1.0, -1.0)) * inverseVP; - v_rgbNE = (fragCoord + vec2(1.0, -1.0)) * inverseVP; - v_rgbSW = (fragCoord + vec2(-1.0, 1.0)) * inverseVP; - v_rgbSE = (fragCoord + vec2(1.0, 1.0)) * inverseVP; - v_rgbM = vec2(fragCoord * inverseVP); -} - - -vec4 fxaa_apply(sampler2D tex, vec2 fragCoord, vec2 resolution) { - mediump vec2 v_rgbNW; - mediump vec2 v_rgbNE; - mediump vec2 v_rgbSW; - mediump vec2 v_rgbSE; - mediump vec2 v_rgbM; - - //compute the texture coords - texcoords(fragCoord, resolution, v_rgbNW, v_rgbNE, v_rgbSW, v_rgbSE, v_rgbM); - - //compute FXAA - return fxaa(tex, fragCoord, resolution, v_rgbNW, v_rgbNE, v_rgbSW, v_rgbSE, v_rgbM); -} - vec3 rgb2hsv(vec3 c) { vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0); vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g)); @@ -160,8 +29,6 @@ vec3 hsv2rgb(vec3 c) { return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y); } -const float FXAA_SCALE = 1.5; - void main() { vec2 uv = (f_pos + 1.0) * 0.5; @@ -170,21 +37,15 @@ void main() { } - //vec4 fxaa_color = fxaa_apply(src_color, uv * screen_res.xy * FXAA_SCALE, screen_res.xy * FXAA_SCALE); - //vec4 fxaa_color = texture(src_color, uv); - ivec2 uv_int = ivec2(uv.x * screen_res.x, uv.y * screen_res.y); - vec4 sample1 = texelFetch(src_color, uv_int, 0); - vec4 sample2 = texelFetch(src_color, uv_int, 1); - vec4 sample3 = texelFetch(src_color, uv_int, 2); - vec4 sample4 = texelFetch(src_color, uv_int, 3); - vec4 fxaa_color = (sample1 + sample2 + sample3 + sample4) / 4.0; + vec4 aa_color = aa_apply(src_color, uv * screen_res.xy, screen_res.xy); - vec4 hsva_color = vec4(rgb2hsv(fxaa_color.rgb), fxaa_color.a); - hsva_color.y *= 1.45; - hsva_color.z *= 0.85; + //vec4 hsva_color = vec4(rgb2hsv(fxaa_color.rgb), fxaa_color.a); + //hsva_color.y *= 1.45; + //hsva_color.z *= 0.85; //hsva_color.z = 1.0 - 1.0 / (1.0 * hsva_color.z + 1.0); - vec4 final_color = fxaa_color; - //vec4 final_color = vec4(hsv2rgb(hsva_color.rgb), hsva_color.a); + //vec4 final_color = vec4(hsv2rgb(hsva_color.rgb), hsva_color.a); + + vec4 final_color = aa_color; if (medium.x == 1u) { final_color *= vec4(0.2, 0.2, 0.8, 1.0); diff --git a/voxygen/src/hud/mod.rs b/voxygen/src/hud/mod.rs index 83edafa662..d18c58ab13 100644 --- a/voxygen/src/hud/mod.rs +++ b/voxygen/src/hud/mod.rs @@ -32,7 +32,7 @@ use social::{Social, SocialTab}; use spell::Spell; use crate::{ - render::{Consts, Globals, Renderer}, + render::{AaMode, Consts, Globals, Renderer}, scene::camera::Camera, settings::ControlSettings, ui::{Ingameable, ScaleMode, Ui}, @@ -158,6 +158,7 @@ pub enum Event { ChangeAudioDevice(String), ChangeMaxFPS(u32), ChangeFOV(u16), + ChangeAaMode(AaMode), CrosshairTransp(f32), CrosshairType(CrosshairType), ToggleXpBar(XpBar), @@ -832,6 +833,9 @@ impl Hud { settings_window::Event::AdjustFOV(new_fov) => { events.push(Event::ChangeFOV(new_fov)); } + settings_window::Event::ChangeAaMode(new_aa_mode) => { + events.push(Event::ChangeAaMode(new_aa_mode)); + } } } } diff --git a/voxygen/src/hud/settings_window.rs b/voxygen/src/hud/settings_window.rs index 8a0d43daa9..bc6a4da576 100644 --- a/voxygen/src/hud/settings_window.rs +++ b/voxygen/src/hud/settings_window.rs @@ -2,7 +2,8 @@ use super::{ img_ids::Imgs, BarNumbers, CrosshairType, Fonts, ShortcutNumbers, Show, XpBar, TEXT_COLOR, }; use crate::{ - ui::{ImageSlider, ScaleMode, ToggleButton}, + render::AaMode, + ui::{ImageSlider, RadioList, ScaleMode, ToggleButton}, GlobalState, }; use conrod_core::{ @@ -76,6 +77,8 @@ widget_ids! { fov_slider, fov_text, fov_value, + aa_radio_buttons, + aa_mode_text, audio_volume_slider, audio_volume_text, sfx_volume_slider, @@ -153,6 +156,7 @@ pub enum Event { AdjustMouseZoom(u32), AdjustViewDistance(u32), AdjustFOV(u16), + ChangeAaMode(AaMode), AdjustMusicVolume(f32), AdjustSfxVolume(f32), ChangeAudioDevice(String), @@ -1196,6 +1200,37 @@ impl<'a> Widget for SettingsWindow<'a> { .font_id(self.fonts.opensans) .color(TEXT_COLOR) .set(state.ids.fov_value, ui); + + // AaMode + Text::new("AntiAliasing Mode") + .down_from(state.ids.fov_slider, 8.0) + .font_size(14) + .font_id(self.fonts.opensans) + .color(TEXT_COLOR) + .set(state.ids.aa_mode_text, ui); + let mode_label_list = [ + (&AaMode::None, "No AA"), + (&AaMode::Fxaa, "FXAA"), + (&AaMode::MsaaX4, "MSAA x4"), + (&AaMode::MsaaX8, "MSAA x8"), + (&AaMode::MsaaX16, "MSAA x16 (experimental)"), + (&AaMode::SsaaX4, "SSAA x4"), + ]; + if let Some((_, mode)) = RadioList::new( + (0..mode_label_list.len()) + .find(|i| *mode_label_list[*i].0 == self.global_state.settings.graphics.aa_mode) + .unwrap_or(0), + self.imgs.check, + self.imgs.check_checked, + &mode_label_list, + ) + .down_from(state.ids.aa_mode_text, 8.0) + .text_color(TEXT_COLOR) + .font_size(12) + .set(state.ids.aa_radio_buttons, ui) + { + events.push(Event::ChangeAaMode(*mode)) + } } // 5) Sound Tab ----------------------------------- diff --git a/voxygen/src/render/mod.rs b/voxygen/src/render/mod.rs index 182e604b6d..2ddee8cb1f 100644 --- a/voxygen/src/render/mod.rs +++ b/voxygen/src/render/mod.rs @@ -52,3 +52,15 @@ use gfx; pub trait Pipeline { type Vertex: Clone + gfx::traits::Pod + gfx::pso::buffer::Structure; } + +use serde_derive::{Deserialize, Serialize}; +/// Anti-aliasing modes +#[derive(PartialEq, Eq, Clone, Copy, Debug, Serialize, Deserialize)] +pub enum AaMode { + None, + Fxaa, + MsaaX4, + MsaaX8, + MsaaX16, + SsaaX4, +} diff --git a/voxygen/src/render/renderer.rs b/voxygen/src/render/renderer.rs index 33e48acafd..b64888faf0 100644 --- a/voxygen/src/render/renderer.rs +++ b/voxygen/src/render/renderer.rs @@ -6,7 +6,7 @@ use super::{ model::{DynamicModel, Model}, pipelines::{figure, fluid, postprocess, skybox, sprite, terrain, ui, Globals, Light, Shadow}, texture::Texture, - Pipeline, RenderError, + AaMode, Pipeline, RenderError, }; use common::assets::{self, watch::ReloadIndicator}; use gfx::{ @@ -71,6 +71,8 @@ pub struct Renderer { postprocess_pipeline: GfxPipeline>, shader_reload_indicator: ReloadIndicator, + + aa_mode: AaMode, } impl Renderer { @@ -80,6 +82,7 @@ impl Renderer { mut factory: gfx_backend::Factory, win_color_view: WinColorView, win_depth_view: WinDepthView, + aa_mode: AaMode, ) -> Result { let mut shader_reload_indicator = ReloadIndicator::new(); @@ -91,11 +94,11 @@ impl Renderer { sprite_pipeline, ui_pipeline, postprocess_pipeline, - ) = create_pipelines(&mut factory, &mut shader_reload_indicator)?; + ) = create_pipelines(&mut factory, aa_mode, &mut shader_reload_indicator)?; let dims = win_color_view.get_dimensions(); let (tgt_color_view, tgt_depth_view, tgt_color_res) = - Self::create_rt_views(&mut factory, (dims.0, dims.1))?; + Self::create_rt_views(&mut factory, (dims.0, dims.1), aa_mode)?; let sampler = factory.create_sampler_linear(); @@ -122,6 +125,8 @@ impl Renderer { postprocess_pipeline, shader_reload_indicator, + + aa_mode, }) } @@ -149,6 +154,19 @@ impl Renderer { (&mut self.win_color_view, &mut self.win_depth_view) } + /// Change the anti-aliasing mode + pub fn set_aa_mode(&mut self, aa_mode: AaMode) -> Result<(), RenderError> { + self.aa_mode = aa_mode; + + // Recreate render target + self.on_resize()?; + + // Recreate pipelines with the new AA mode + self.recreate_pipelines(); + + Ok(()) + } + /// Resize internal render targets to match window render target dimensions. pub fn on_resize(&mut self) -> Result<(), RenderError> { let dims = self.win_color_view.get_dimensions(); @@ -156,7 +174,7 @@ impl Renderer { // Avoid panics when creating texture with w,h of 0,0. if dims.0 != 0 && dims.1 != 0 { let (tgt_color_view, tgt_depth_view, tgt_color_res) = - Self::create_rt_views(&mut self.factory, (dims.0, dims.1))?; + Self::create_rt_views(&mut self.factory, (dims.0, dims.1), self.aa_mode)?; self.tgt_color_res = tgt_color_res; self.tgt_color_view = tgt_color_view; self.tgt_depth_view = tgt_depth_view; @@ -168,8 +186,26 @@ impl Renderer { fn create_rt_views( factory: &mut gfx_device_gl::Factory, size: (u16, u16), + aa_mode: AaMode, ) -> Result<(TgtColorView, TgtDepthView, TgtColorRes), RenderError> { - let kind = gfx::texture::Kind::D2(size.0, size.1, gfx::texture::AaMode::Multi(4)); + let kind = match aa_mode { + AaMode::None | AaMode::Fxaa => { + gfx::texture::Kind::D2(size.0, size.1, gfx::texture::AaMode::Single) + } + // TODO: Ensure sampling in the shader is exactly between the 4 texels + AaMode::SsaaX4 => { + gfx::texture::Kind::D2(size.0 * 2, size.1 * 2, gfx::texture::AaMode::Single) + } + AaMode::MsaaX4 => { + gfx::texture::Kind::D2(size.0, size.1, gfx::texture::AaMode::Multi(4)) + } + AaMode::MsaaX8 => { + gfx::texture::Kind::D2(size.0, size.1, gfx::texture::AaMode::Multi(8)) + } + AaMode::MsaaX16 => { + gfx::texture::Kind::D2(size.0, size.1, gfx::texture::AaMode::Multi(16)) + } + }; let levels = 1; let color_cty = <::Channel as gfx::format::ChannelTyped @@ -222,29 +258,38 @@ impl Renderer { // If the shaders files were changed attempt to recreate the shaders if self.shader_reload_indicator.reloaded() { - match create_pipelines(&mut self.factory, &mut self.shader_reload_indicator) { - Ok(( - skybox_pipeline, - figure_pipeline, - terrain_pipeline, - fluid_pipeline, - sprite_pipeline, - ui_pipeline, - postprocess_pipeline, - )) => { - self.skybox_pipeline = skybox_pipeline; - self.figure_pipeline = figure_pipeline; - self.terrain_pipeline = terrain_pipeline; - self.fluid_pipeline = fluid_pipeline; - self.sprite_pipeline = sprite_pipeline; - self.ui_pipeline = ui_pipeline; - self.postprocess_pipeline = postprocess_pipeline; - } - Err(e) => error!( - "Could not recreate shaders from assets due to an error: {:#?}", - e - ), + self.recreate_pipelines(); + } + } + + /// Recreate the pipelines + fn recreate_pipelines(&mut self) { + match create_pipelines( + &mut self.factory, + self.aa_mode, + &mut self.shader_reload_indicator, + ) { + Ok(( + skybox_pipeline, + figure_pipeline, + terrain_pipeline, + fluid_pipeline, + sprite_pipeline, + ui_pipeline, + postprocess_pipeline, + )) => { + self.skybox_pipeline = skybox_pipeline; + self.figure_pipeline = figure_pipeline; + self.terrain_pipeline = terrain_pipeline; + self.fluid_pipeline = fluid_pipeline; + self.sprite_pipeline = sprite_pipeline; + self.ui_pipeline = ui_pipeline; + self.postprocess_pipeline = postprocess_pipeline; } + Err(e) => error!( + "Could not recreate shaders from assets due to an error: {:#?}", + e + ), } } @@ -595,6 +640,7 @@ struct GfxPipeline { /// Creates all the pipelines used to render. fn create_pipelines( factory: &mut gfx_backend::Factory, + aa_mode: AaMode, shader_reload_indicator: &mut ReloadIndicator, ) -> Result< ( @@ -624,12 +670,29 @@ fn create_pipelines( assets::load_watched::("voxygen.shaders.include.random", shader_reload_indicator) .unwrap(); + let anti_alias = assets::load_watched::( + &[ + "voxygen.shaders.antialias.", + match aa_mode { + AaMode::None | AaMode::SsaaX4 => "none", + AaMode::Fxaa => "fxaa", + AaMode::MsaaX4 => "msaa-x4", + AaMode::MsaaX8 => "msaa-x8", + AaMode::MsaaX16 => "msaa-x16", + }, + ] + .concat(), + shader_reload_indicator, + ) + .unwrap(); + let mut include_ctx = IncludeContext::new(); include_ctx.include("globals.glsl", &globals); include_ctx.include("sky.glsl", &sky); include_ctx.include("light.glsl", &light); include_ctx.include("srgb.glsl", &srgb); include_ctx.include("random.glsl", &random); + include_ctx.include("anti-aliasing.glsl", &anti_alias); // Construct a pipeline for rendering skyboxes let skybox_pipeline = create_pipeline( diff --git a/voxygen/src/session.rs b/voxygen/src/session.rs index 0ad2b8dc54..fbdd326786 100644 --- a/voxygen/src/session.rs +++ b/voxygen/src/session.rs @@ -455,7 +455,17 @@ impl PlayState for SessionState { HudEvent::ChangeFOV(new_fov) => { global_state.settings.graphics.fov = new_fov; global_state.settings.save_to_file_warn(); - &self.scene.camera_mut().set_fov_deg(new_fov); + self.scene.camera_mut().set_fov_deg(new_fov); + } + HudEvent::ChangeAaMode(new_aa_mode) => { + // Do this first so if it crashes the setting isn't saved :) + global_state + .window + .renderer_mut() + .set_aa_mode(new_aa_mode) + .unwrap(); + global_state.settings.graphics.aa_mode = new_aa_mode; + global_state.settings.save_to_file_warn(); } } } diff --git a/voxygen/src/settings.rs b/voxygen/src/settings.rs index 02196ee77a..e2603baada 100644 --- a/voxygen/src/settings.rs +++ b/voxygen/src/settings.rs @@ -1,5 +1,6 @@ use crate::{ hud::{BarNumbers, CrosshairType, ShortcutNumbers, XpBar}, + render::AaMode, ui::ScaleMode, window::KeyMouse, }; @@ -160,6 +161,7 @@ pub struct GraphicsSettings { pub view_distance: u32, pub max_fps: u32, pub fov: u16, + pub aa_mode: AaMode, } impl Default for GraphicsSettings { @@ -168,6 +170,7 @@ impl Default for GraphicsSettings { view_distance: 5, max_fps: 60, fov: 75, + aa_mode: AaMode::Fxaa, } } } diff --git a/voxygen/src/ui/mod.rs b/voxygen/src/ui/mod.rs index bb03715d9e..a8f8f5fd53 100644 --- a/voxygen/src/ui/mod.rs +++ b/voxygen/src/ui/mod.rs @@ -15,6 +15,7 @@ pub use widgets::{ image_frame::ImageFrame, image_slider::ImageSlider, ingame::{Ingame, IngameAnchor, Ingameable}, + radio_list::RadioList, toggle_button::ToggleButton, tooltip::{Tooltip, TooltipManager, Tooltipable}, }; diff --git a/voxygen/src/ui/widgets/mod.rs b/voxygen/src/ui/widgets/mod.rs index 3cdc003505..7c26721f56 100644 --- a/voxygen/src/ui/widgets/mod.rs +++ b/voxygen/src/ui/widgets/mod.rs @@ -1,5 +1,6 @@ pub mod image_frame; pub mod image_slider; pub mod ingame; +pub mod radio_list; pub mod toggle_button; pub mod tooltip; diff --git a/voxygen/src/ui/widgets/radio_list.rs b/voxygen/src/ui/widgets/radio_list.rs new file mode 100644 index 0000000000..0479479d5c --- /dev/null +++ b/voxygen/src/ui/widgets/radio_list.rs @@ -0,0 +1,202 @@ +use conrod_core::{ + builder_methods, image, text, + widget::{self, button}, + widget_ids, Color, FontSize, Positionable, Rect, Sizeable, Widget, WidgetCommon, +}; + +#[derive(Clone, WidgetCommon)] +pub struct RadioList<'a, T> { + #[conrod(common_builder)] + common: widget::CommonBuilder, + f_image: button::Image, + t_image: button::Image, + selected: usize, + options_labels: &'a [(&'a T, &'a str)], + label_style: widget::text::Style, + label_spacing: f64, + button_spacing: [f64; 2], + button_dims: [f64; 2], +} + +widget_ids! { + struct Ids { + buttons[], + labels[], + } +} + +pub struct State { + ids: Ids, +} + +impl<'a, T> RadioList<'a, T> { + pub fn new( + selected: usize, + f_image_id: image::Id, + t_image_id: image::Id, + options_labels: &'a [(&'a T, &'a str)], + ) -> Self { + Self { + common: widget::CommonBuilder::default(), + f_image: button::Image { + image_id: f_image_id, + hover_image_id: None, + press_image_id: None, + src_rect: None, + color: button::ImageColor::None, + }, + t_image: button::Image { + image_id: t_image_id, + hover_image_id: None, + press_image_id: None, + src_rect: None, + color: button::ImageColor::None, + }, + selected, + label_style: widget::text::Style::default(), + options_labels, + label_spacing: 10.0, + button_spacing: [5.0, 5.0], + button_dims: [15.0, 15.0], + } + } + + pub fn source_rectangle(mut self, rect: Rect) -> Self { + self.f_image.src_rect = Some(rect); + self.t_image.src_rect = Some(rect); + self + } + + pub fn image_colors(mut self, f_color: Color, t_color: Color) -> Self { + self.f_image.color = button::ImageColor::Normal(f_color); + self.t_image.color = button::ImageColor::Normal(t_color); + self + } + + pub fn image_color_with_feedback(mut self, f_color: Color, t_color: Color) -> Self { + self.f_image.color = button::ImageColor::WithFeedback(f_color); + self.t_image.color = button::ImageColor::WithFeedback(t_color); + self + } + + pub fn hover_images(mut self, f_id: image::Id, t_id: image::Id) -> Self { + self.f_image.hover_image_id = Some(f_id); + self.t_image.hover_image_id = Some(t_id); + self + } + + pub fn press_images(mut self, f_id: image::Id, t_id: image::Id) -> Self { + self.f_image.press_image_id = Some(f_id); + self.t_image.press_image_id = Some(t_id); + self + } + builder_methods! { + pub text_color { label_style.color = Some(Color) } + pub font_size { label_style.font_size = Some(FontSize) } + pub justify { label_style.justify = Some(text::Justify) } + pub line_spacing { label_style.line_spacing = Some(f64) } + pub label_spacing { label_spacing = f64 } + pub button_spacing { button_spacing = [f64; 2] } + pub button_dims { button_dims = [f64; 2] } + } +} + +impl<'a, T> Widget for RadioList<'a, T> { + type State = State; + type Style = (); + type Event = Option<(usize, &'a T)>; + + fn init_state(&self, id_gen: widget::id::Generator) -> Self::State { + State { + ids: Ids::new(id_gen), + } + } + + fn style(&self) -> Self::Style { + () + } + + fn update(self, args: widget::UpdateArgs) -> Self::Event { + let widget::UpdateArgs { + id, + state, + ui, + rect, + .. + } = args; + let Self { + f_image, + t_image, + selected, + options_labels, + label_style, + label_spacing, + button_spacing, + button_dims, + .. + } = self; + + // Ensure we have enough widget ids + let num_items = options_labels.len(); + if state.ids.buttons.len() < num_items || state.ids.labels.len() < num_items { + state.update(|s| { + s.ids + .buttons + .resize(num_items, &mut ui.widget_id_generator()); + s.ids + .labels + .resize(num_items, &mut ui.widget_id_generator()); + }); + } + + // Check if the button was clicked. + // (Can't use `.set().was_clicked()` because we are changing the image after setting the + // widget, which causes flickering since it takes a frame to change after the mouse button + // is lifted). + let current_selection = (0..num_items) + .find(|i| { + ui.widget_input(state.ids.buttons[*i]) + .clicks() + .left() + .count() + % 2 + == 1 + }) + .unwrap_or(selected); + + let (x, y, w, h) = rect.x_y_w_h(); + for i in 0..num_items { + let image = if i == current_selection { + t_image + } else { + f_image + }; + // Button + let mut button = button::Button::image(image.image_id) + .wh(button_dims) + //TODO: implement default width / height functions + .x_y( + x - w / 2.0 + button_spacing[0], + y - h / 2.0 + - i as f64 * (button_dims[1] + button_spacing[1]) + - button_spacing[1], + ) + .parent(id); + button.show = image; + button.set(state.ids.buttons[i], ui); + // Label + widget::Text::new(options_labels[i].1) + .graphics_for(state.ids.buttons[i]) + .parent(id) + .with_style(label_style) + .right_from(state.ids.buttons[i], label_spacing) + .set(state.ids.labels[i], ui); + } + + if current_selection != selected { + Some((current_selection, options_labels[current_selection].0)) + } else { + None + } + } +} diff --git a/voxygen/src/window.rs b/voxygen/src/window.rs index a0ccf0154e..5564e86a04 100644 --- a/voxygen/src/window.rs +++ b/voxygen/src/window.rs @@ -225,7 +225,13 @@ impl Window { Ok(Self { events_loop, - renderer: Renderer::new(device, factory, win_color_view, win_depth_view)?, + renderer: Renderer::new( + device, + factory, + win_color_view, + win_depth_view, + settings.graphics.aa_mode, + )?, window, cursor_grabbed: false, pan_sensitivity: settings.gameplay.pan_sensitivity,