diff --git a/StarterNG.Tests/ConfigFileTests.cs b/StarterNG.Tests/ConfigFileTests.cs index 1eb356b..6618e99 100644 --- a/StarterNG.Tests/ConfigFileTests.cs +++ b/StarterNG.Tests/ConfigFileTests.cs @@ -17,6 +17,31 @@ public class ConfigFileTests Assert.Equal($"a 1{nl}b 2{nl}", ConfigFile.Parse("a 1\r\nb 2\r\n").ToText()); } + [Fact] + public void A_line_above_another_moves_right_below_it() + { + var config = ConfigFile.Parse("gfx.antialiasing taa\nwidth 800\nmultisampling 2\nheight 600\n"); + + config.MoveAfter("gfx.antialiasing", "multisampling"); + + Assert.Equal(new[] { "width 800", "multisampling 2", "gfx.antialiasing taa", "height 600", "" }, Lines(config)); + Assert.Equal("taa", config.GetRaw("gfx.antialiasing")); + Assert.Equal("600", config.GetRaw("height")); + } + + [Theory] + [InlineData("multisampling 2\nwidth 800\ngfx.antialiasing taa\n")] + [InlineData("multisampling 2\n")] + [InlineData("gfx.antialiasing taa\n")] + public void A_line_below_or_alone_stays_where_it_is(string text) + { + var config = ConfigFile.Parse(text); + + config.MoveAfter("gfx.antialiasing", "multisampling"); + + Assert.Equal(ConfigFile.Parse(text).ToText(), config.ToText()); + } + [Fact] public void Removing_a_key_comments_every_line_that_sets_it() { diff --git a/StarterNG.Tests/GraphicsPresetTests.cs b/StarterNG.Tests/GraphicsPresetTests.cs index 6a7b4c4..4331268 100644 --- a/StarterNG.Tests/GraphicsPresetTests.cs +++ b/StarterNG.Tests/GraphicsPresetTests.cs @@ -33,7 +33,7 @@ public class GraphicsPresetTests var topKeys = store.Render(top); var differing = lowKeys.Keys.Union(topKeys.Keys) .Where(key => lowKeys.GetValueOrDefault(key) != topKeys.GetValueOrDefault(key)) - .Where(key => !key.StartsWith("gfx.framebuffer", StringComparison.Ordinal)); + .Where(key => !key.StartsWith("gfx.framebuffer", StringComparison.Ordinal) && key != "gfx.buffer.percent"); Assert.Equal(GraphicsPreset.Keys.OrderBy(k => k), differing.OrderBy(k => k)); } @@ -103,7 +103,7 @@ public class GraphicsPresetTests var differing = own.Keys.Union(high.Keys) .Where(key => own.GetValueOrDefault(key) != high.GetValueOrDefault(key)) .Where(key => !GraphicsPreset.RowKeys.Contains(key)) - .Where(key => !key.StartsWith("gfx.framebuffer", StringComparison.Ordinal)); + .Where(key => !key.StartsWith("gfx.framebuffer", StringComparison.Ordinal) && key != "gfx.buffer.percent"); Assert.Equal(new[] { "gfx.reflections.fidelity", "gfx.reflections.framerate", "gfx.shadows.cab.range", "gfx.smoke.fidelity", "splinefidelity" }, differing.OrderBy(k => k)); @@ -144,6 +144,113 @@ public class GraphicsPresetTests Assert.Same(GraphicsPreset.Low, GraphicsPreset.Matching(again.Settings)); } + [Fact] + public void High_is_what_the_new_simulator_does_with_nothing_set() + { + Assert.Equal(GraphicsPreset.High.NewRenderer, GraphicsNewRenderer.Of(Defaults())); + } + + [Fact] + public void The_new_renderers_scales_never_go_down() + { + void WordsClimb(string name, Func word) + { + var words = SimulatorKeys.Named(name).Words!; + var places = GraphicsPreset.All.Select(p => Array.IndexOf(words, word(p.NewRenderer))).ToList(); + Assert.DoesNotContain(-1, places); + Assert.Equal(places.OrderBy(place => place), places); + } + + void TurnsOn(Func on) + { + var values = GraphicsPreset.All.Select(p => on(p.NewRenderer)).ToList(); + Assert.Equal(values.OrderBy(value => value), values); + } + + WordsClimb("gfx.lights.shadows", r => r.LampShadows); + WordsClimb("gfx.contactshadows", r => r.ContactShadows); + WordsClimb("gfx.postfx.ao", r => r.Occlusion); + WordsClimb("gfx.postfx.ao.quality", r => r.OcclusionQuality); + WordsClimb("gfx.cabprobe", r => r.CabProbe); + WordsClimb("gfx.ssr", r => r.ScreenReflections); + WordsClimb("gfx.fog", r => r.Fog); + WordsClimb("gfx.fog.quality", r => r.FogQuality); + WordsClimb("gfx.clouds", r => r.Clouds); + + TurnsOn(r => r.OcclusionLighting); + TurnsOn(r => r.CabProbeReflections); + TurnsOn(r => r.RayTracing); + TurnsOn(r => r.CloudShadows); + TurnsOn(r => r.Grass); + TurnsOn(r => r.Bloom); + + Climbs(p => p.NewRenderer.GrassDistance, 0); + Climbs(p => p.NewRenderer.GrassFade, 0); + Climbs(p => p.NewRenderer.GrassDensity, 0); + Climbs(p => p.NewRenderer.Precipitation, 0); + // Frames between refreshes, so the dearer the fewer. + Climbs(p => -p.NewRenderer.CabProbeInterval, 2); + } + + [Fact] + public void Ultra_is_the_top_of_the_new_renderers_scales() + { + var ultra = GraphicsPreset.Ultra.NewRenderer; + double Top(string key) => SimulatorKeys.Named(key).Maximum; + string TopWord(string key) => SimulatorKeys.Named(key).Words!.Last(); + + Assert.Equal(TopWord("gfx.lights.shadows"), ultra.LampShadows); + Assert.Equal(TopWord("gfx.contactshadows"), ultra.ContactShadows); + Assert.Equal(TopWord("gfx.postfx.ao"), ultra.Occlusion); + Assert.Equal(TopWord("gfx.postfx.ao.quality"), ultra.OcclusionQuality); + Assert.Equal(TopWord("gfx.cabprobe"), ultra.CabProbe); + Assert.Equal(TopWord("gfx.ssr"), ultra.ScreenReflections); + Assert.Equal(TopWord("gfx.fog"), ultra.Fog); + Assert.Equal(TopWord("gfx.fog.quality"), ultra.FogQuality); + Assert.Equal(TopWord("gfx.clouds"), ultra.Clouds); + Assert.Equal(Top("gfx.grass.distance"), ultra.GrassDistance); + Assert.Equal(Top("gfx.grass.fadedistance"), ultra.GrassFade); + Assert.Equal(Top("gfx.grass.density"), ultra.GrassDensity); + Assert.Equal(Top("gfx.precipitation.particles"), ultra.Precipitation); + } + + [Fact] + public void Grass_fades_out_beyond_where_it_is_drawn_in_full() + { + Assert.All(GraphicsPreset.All, p => Assert.True(p.NewRenderer.GrassFade > p.NewRenderer.GrassDistance, p.NameKey)); + } + + [Fact] + public void A_level_set_in_the_new_renderers_rows_is_told_from_the_others() + { + var settings = Defaults().Copy(); + GraphicsPreset.Medium.ApplyTo(settings); + Assert.Same(GraphicsPreset.Medium, GraphicsPreset.Matching(settings)); + + settings.Extra["gfx.fog.quality"] = "ultra"; + + Assert.Null(GraphicsPreset.Matching(settings)); + } + + [Fact] + public void What_is_left_to_the_player_stays_through_a_level() + { + var settings = Defaults().Copy(); + settings.Extra["gfx.antialiasing"] = "taa"; + settings.Extra["gfx.framegeneration"] = "x2"; + settings.Extra["gfx.vulkan.device"] = "RTX"; + + foreach (var preset in GraphicsPreset.All) + { + preset.ApplyTo(settings); + + Assert.Same(preset, GraphicsPreset.Matching(settings)); + Assert.Equal("taa", settings.Extra["gfx.antialiasing"]); + Assert.Equal("x2", settings.Extra["gfx.framegeneration"]); + Assert.Equal("RTX", settings.Extra["gfx.vulkan.device"]); + } + } + [Fact] public void Ultra_is_the_top_of_everything_the_screen_offers() { @@ -503,6 +610,11 @@ public class GraphicsPresetTests OnSteps(preset.SleeperDistance, GraphicsPreset.SleeperKey); OnSteps(preset.ReflectedInstances, GraphicsPreset.ReflectedInstancesKey); OnSteps(preset.ReflectedVehicles, GraphicsPreset.ReflectedVehiclesKey); + OnSteps(preset.NewRenderer.CabProbeInterval, "gfx.cabprobe.interval"); + OnSteps(preset.NewRenderer.GrassDistance, "gfx.grass.distance"); + OnSteps(preset.NewRenderer.GrassFade, "gfx.grass.fadedistance"); + OnSteps(preset.NewRenderer.GrassDensity, "gfx.grass.density"); + OnSteps(preset.NewRenderer.Precipitation, "gfx.precipitation.particles"); Assert.True(Steps.IndexOf(Steps.Anisotropy, preset.Anisotropy) >= 0, preset.NameKey); Assert.True(Steps.IndexOf(Steps.MultisamplingLevels, preset.Multisampling) >= 0, preset.NameKey); Assert.True(Steps.IndexOf(Steps.ReflectionUpdates, preset.ReflectionsRate) >= 0, preset.NameKey); diff --git a/StarterNG.Tests/SettingReachTests.cs b/StarterNG.Tests/SettingReachTests.cs index 77e6e4f..68d633e 100644 --- a/StarterNG.Tests/SettingReachTests.cs +++ b/StarterNG.Tests/SettingReachTests.cs @@ -72,6 +72,32 @@ public class SettingReachTests Assert.Equal(expected, SettingReach.Why("sleeperdistance", Linux(renderer))); } + [Theory] + [InlineData("vulkan", IdleReason.None)] + [InlineData("vulkan14", IdleReason.None)] + [InlineData("full", IdleReason.VulkanOnly)] + [InlineData("gl46", IdleReason.VulkanOnly)] + [InlineData("legacy", IdleReason.VulkanOnly)] + public void What_only_vulkan_reads_follows_the_renderer(string renderer, IdleReason expected) + { + Assert.Equal(expected, SettingReach.Why("gfx.dlss.preset", Linux(renderer))); + Assert.Equal(expected, SettingReach.Why("gfx.framegeneration", Linux(renderer))); + Assert.Equal(expected, SettingReach.Why("gfx.raytracing", Linux(renderer))); + // Both renderers of the new simulator read this one. + Assert.Equal(IdleReason.None, SettingReach.Why("gfx.postfx.ao", Linux(renderer))); + } + + [Theory] + [InlineData("vulkan")] + [InlineData("vulkan14")] + [InlineData("opengl")] + [InlineData("gl46")] + public void The_new_simulators_renderers_read_what_the_full_one_does(string renderer) + { + Assert.Equal(IdleReason.None, SettingReach.Why("gfx.envmap.enabled", Linux(renderer))); + Assert.Equal(IdleReason.None, SettingReach.Why("multisampling", Linux(renderer))); + } + [Theory] [InlineData("full", IdleReason.None)] [InlineData("legacy", IdleReason.None)] diff --git a/StarterNG.Tests/SettingsStoreTests.cs b/StarterNG.Tests/SettingsStoreTests.cs index 6d8c21f..2198dcb 100644 --- a/StarterNG.Tests/SettingsStoreTests.cs +++ b/StarterNG.Tests/SettingsStoreTests.cs @@ -858,6 +858,9 @@ public class SettingsStoreTests [InlineData("default")] [InlineData("simpleshader")] [InlineData("off")] + [InlineData("opengl")] + [InlineData("gl46")] + [InlineData("vulkan14")] public void A_renderer_word_the_simulator_reads_as_another_stays_until_changed(string word) { var installation = WithHome(new InMemoryFileSystem().WithFile(UserIni, $"gfxrenderer {word}\n")); @@ -873,6 +876,7 @@ public class SettingsStoreTests [InlineData(1, "legacy")] [InlineData(2, "simple")] [InlineData(3, "experimental")] + [InlineData(4, "vulkan")] public void A_chosen_renderer_is_saved_as_the_simulator_reads_it(int engine, string saved) { // application.cpp knows full (or default), experimental and simple; every @@ -888,6 +892,92 @@ public class SettingsStoreTests Assert.Contains($"gfxrenderer {saved}\n", SavedLines(installation)); } + [Theory] + [InlineData("full", 0)] + [InlineData("gl46", 0)] + [InlineData("opengl", 0)] + [InlineData("vulkan", 4)] + [InlineData("VULKAN14", 4)] + [InlineData("simpleshader", 1)] + public void A_renderer_word_stands_where_the_simulator_reads_it(string word, int engine) + { + Assert.Equal(engine, SimulatorSettings.RenderEngineOf(word)); + } + + [Fact] + public void The_buffer_is_written_for_old_and_new_simulators() + { + var installation = WithHome(new InMemoryFileSystem().WithFile(UserIni, "width 1000\nheight 500\n")); + installation.SettingsStore.Load(); + installation.SettingsStore.Settings.BufferScalePercent = 50; + + installation.SettingsStore.Save(); + + string saved = SavedLines(installation); + Assert.Contains("gfx.buffer.percent 50\n", saved); + Assert.Contains("gfx.framebuffer.width 500\n", saved); + Assert.Contains("gfx.framebuffer.height 250\n", saved); + } + + [Theory] + [InlineData("gfx.buffer.percent 0.5\n", 50)] + [InlineData("gfx.buffer.percent 75\n", 75)] + [InlineData("gfx.buffer.percent 10\n", 25)] + [InlineData("gfx.buffer.percent 300\n", 200)] + [InlineData("starter.bufferscale 60\n", 60)] + [InlineData("starter.bufferscale 60\ngfx.buffer.percent 80\n", 80)] + public void The_buffer_is_read_from_the_key_the_new_simulator_goes_by(string ini, int percent) + { + var installation = WithHome(new InMemoryFileSystem().WithFile(UserIni, ini)); + + installation.SettingsStore.Load(); + + Assert.Equal(percent, installation.SettingsStore.Settings.BufferScalePercent); + } + + [Fact] + public void Anti_aliasing_is_saved_below_multisampling_for_the_later_line_to_count() + { + var installation = WithHome(new InMemoryFileSystem().WithFile(UserIni, "gfx.antialiasing taa\nwidth 800\nmultisampling 2\n")); + installation.SettingsStore.Load(); + + installation.SettingsStore.Save(); + + string saved = SavedLines(installation); + Assert.True(saved.IndexOf("multisampling ", StringComparison.Ordinal) < saved.IndexOf("gfx.antialiasing taa", StringComparison.Ordinal), saved); + } + + [Fact] + public void Anti_aliasing_left_to_multisampling_goes_from_the_file() + { + var installation = WithHome(new InMemoryFileSystem().WithFile(UserIni, "multisampling 2\ngfx.antialiasing taa\n")); + installation.SettingsStore.Load(); + + installation.SettingsStore.Settings.Extra["gfx.antialiasing"] = ""; + installation.SettingsStore.Save(); + + Assert.DoesNotContain("\ngfx.antialiasing", SavedLines(installation)); + } + + [Theory] + [InlineData("TAA", "taa")] + [InlineData(" dlaa ", "dlaa")] + [InlineData("", "")] + [InlineData("fxaa", "")] + public void A_choice_reads_the_words_it_offers_and_else_the_default(string raw, string word) + { + Assert.Equal(word, SimulatorKeys.Named("gfx.antialiasing").ParseChoice(raw)); + } + + [Theory] + [InlineData("4 6", "4 6")] + [InlineData("4 6", "4 6")] + [InlineData("5 5", "3 4")] + public void A_choice_of_two_numbers_is_one_word(string raw, string word) + { + Assert.Equal(word, SimulatorKeys.Named("gfx.postfx.ao.quality").ParseChoice(raw)); + } + [Fact] public void A_field_of_view_down_to_ten_degrees_survives_a_save() { diff --git a/StarterNG/Application/SettingsStore.cs b/StarterNG/Application/SettingsStore.cs index ca92875..6bcdbed 100644 --- a/StarterNG/Application/SettingsStore.cs +++ b/StarterNG/Application/SettingsStore.cs @@ -164,6 +164,8 @@ public sealed class SettingsStore else _config.Set(change.Key, change.After); } + // Each sets the mode as the simulator reads it, so this one has to come later to count. + _config.MoveAfter("gfx.antialiasing", "multisampling"); _synced = Render(Settings); try diff --git a/StarterNG/Classes/ConfigFile.cs b/StarterNG/Classes/ConfigFile.cs index 63be091..0769175 100644 --- a/StarterNG/Classes/ConfigFile.cs +++ b/StarterNG/Classes/ConfigFile.cs @@ -161,6 +161,21 @@ public sealed class ConfigFile Reindex(); } + /// + /// Puts the line of right below that of when + /// it is above it, for keys of which the simulator, reading top down, heeds the later. + /// + public void MoveAfter(string key, string anchor) + { + if (!_index.TryGetValue(key, out int from) || !_index.TryGetValue(anchor, out int to) || from > to) + return; + + string line = _lines[from]; + _lines.RemoveAt(from); + _lines.Insert(to, line); + Reindex(); + } + /// Comments out every line setting the key: the last one alone would bring the one before it back. public void Remove(string key) { diff --git a/StarterNG/Domain/Settings/GraphicsPreset.cs b/StarterNG/Domain/Settings/GraphicsPreset.cs index 78abedd..a1ae91c 100644 --- a/StarterNG/Domain/Settings/GraphicsPreset.cs +++ b/StarterNG/Domain/Settings/GraphicsPreset.cs @@ -119,6 +119,107 @@ public sealed record GraphicsTop( } } +/// +/// What only the simulators with the OpenGL 4.6 and Vulkan 1.4 renderers read, the others pass it +/// over. High is what they do with nothing set. Left to the player: anti-aliasing by TAA or DLSS, +/// frame generation, Reflex, the choice of the card, automatic exposure and the fitted cascades. +/// +/// gfx.cabprobe.interval, in frames: the lower the dearer. +/// gfx.raytracing: the Vulkan renderer's alone, OpenGL passes it over. +public sealed record GraphicsNewRenderer( + string LampShadows, + string ContactShadows, + string Occlusion, + string OcclusionQuality, + bool OcclusionLighting, + string CabProbe, + double CabProbeInterval, + bool CabProbeReflections, + string ScreenReflections, + bool RayTracing, + string Fog, + string FogQuality, + string Clouds, + bool CloudShadows, + bool Grass, + double GrassDistance, + double GrassFade, + double GrassDensity, + double Precipitation, + bool Bloom) +{ + private const string LampShadowsKey = "gfx.lights.shadows"; + private const string ContactShadowsKey = "gfx.contactshadows"; + private const string OcclusionKey = "gfx.postfx.ao"; + private const string OcclusionQualityKey = "gfx.postfx.ao.quality"; + private const string OcclusionLightingKey = "gfx.postfx.ao.lighting"; + private const string CabProbeKey = "gfx.cabprobe"; + private const string CabProbeIntervalKey = "gfx.cabprobe.interval"; + private const string CabProbeReflectionsKey = "gfx.cabprobe.reflections"; + private const string ScreenReflectionsKey = "gfx.ssr"; + private const string RayTracingKey = "gfx.raytracing"; + private const string FogKey = "gfx.fog"; + private const string FogQualityKey = "gfx.fog.quality"; + private const string CloudsKey = "gfx.clouds"; + private const string CloudShadowsKey = "gfx.clouds.shadows"; + private const string GrassKey = "gfx.grass"; + private const string GrassDistanceKey = "gfx.grass.distance"; + private const string GrassFadeKey = "gfx.grass.fadedistance"; + private const string GrassDensityKey = "gfx.grass.density"; + private const string PrecipitationKey = "gfx.precipitation.particles"; + private const string BloomKey = "gfx.postfx.bloom"; + + /// The eu07.ini keys these values live in, all of them rows of . + public static readonly IReadOnlyList Keys = new[] + { + LampShadowsKey, ContactShadowsKey, OcclusionKey, OcclusionQualityKey, OcclusionLightingKey, + CabProbeKey, CabProbeIntervalKey, CabProbeReflectionsKey, ScreenReflectionsKey, RayTracingKey, + FogKey, FogQualityKey, CloudsKey, CloudShadowsKey, + GrassKey, GrassDistanceKey, GrassFadeKey, GrassDensityKey, PrecipitationKey, BloomKey + }; + + public static GraphicsNewRenderer Of(SimulatorSettings s) => + new(WordOf(s, LampShadowsKey), WordOf(s, ContactShadowsKey), WordOf(s, OcclusionKey), WordOf(s, OcclusionQualityKey), + GraphicsPreset.ToggleOf(s, OcclusionLightingKey), WordOf(s, CabProbeKey), GraphicsPreset.NumberOf(s, CabProbeIntervalKey), + GraphicsPreset.ToggleOf(s, CabProbeReflectionsKey), WordOf(s, ScreenReflectionsKey), GraphicsPreset.ToggleOf(s, RayTracingKey), + WordOf(s, FogKey), WordOf(s, FogQualityKey), WordOf(s, CloudsKey), GraphicsPreset.ToggleOf(s, CloudShadowsKey), + GraphicsPreset.ToggleOf(s, GrassKey), GraphicsPreset.NumberOf(s, GrassDistanceKey), GraphicsPreset.NumberOf(s, GrassFadeKey), + GraphicsPreset.NumberOf(s, GrassDensityKey), GraphicsPreset.NumberOf(s, PrecipitationKey), GraphicsPreset.ToggleOf(s, BloomKey)); + + public void ApplyTo(SimulatorSettings s) + { + void Number(string key, double value) => s.Extra[key] = value.ToString(CultureInfo.InvariantCulture); + void Toggle(string key, bool value) => s.Extra[key] = SimulatorKey.FormatToggle(value); + + s.Extra[LampShadowsKey] = LampShadows; + s.Extra[ContactShadowsKey] = ContactShadows; + s.Extra[OcclusionKey] = Occlusion; + s.Extra[OcclusionQualityKey] = OcclusionQuality; + Toggle(OcclusionLightingKey, OcclusionLighting); + s.Extra[CabProbeKey] = CabProbe; + Number(CabProbeIntervalKey, CabProbeInterval); + Toggle(CabProbeReflectionsKey, CabProbeReflections); + s.Extra[ScreenReflectionsKey] = ScreenReflections; + Toggle(RayTracingKey, RayTracing); + s.Extra[FogKey] = Fog; + s.Extra[FogQualityKey] = FogQuality; + s.Extra[CloudsKey] = Clouds; + Toggle(CloudShadowsKey, CloudShadows); + Toggle(GrassKey, Grass); + Number(GrassDistanceKey, GrassDistance); + Number(GrassFadeKey, GrassFade); + Number(GrassDensityKey, GrassDensity); + Number(PrecipitationKey, Precipitation); + Toggle(BloomKey, Bloom); + } + + private static string WordOf(SimulatorSettings s, string name) + { + var key = SimulatorKeys.Named(name); + return key.ParseChoice(key.RawIn(s)); + } +} + /// /// A level of detail set in one go: what weighs on the graphics card. Every scale climbs in /// even steps from to , as far as it has steps for six @@ -161,6 +262,7 @@ public sealed record GraphicsPreset( double ReflectedInstances, double ReflectedVehicles, int CabShadowsRange, + GraphicsNewRenderer NewRenderer, GraphicsExtras? Extras = null, GraphicsTop? Top = null) { @@ -174,7 +276,7 @@ public sealed record GraphicsPreset( public static readonly IReadOnlyList RowKeys = new[] { DrawRangeKey, DistanceFactorKey, SleeperKey, ReflectedInstancesKey, ReflectedVehiclesKey - }; + }.Concat(GraphicsNewRenderer.Keys).ToList(); /// The eu07.ini keys a level sets. public static readonly IReadOnlyList Keys = new[] @@ -184,7 +286,7 @@ public sealed record GraphicsPreset( "gfx.envmap.enabled", "gfx.reflections.fidelity", "gfx.reflections.framerate", "gfx.extraeffects", "splinefidelity", DrawRangeKey, DistanceFactorKey, "starter.bufferscale", SleeperKey, "gfx.drawrange.factor.max", ReflectedInstancesKey, ReflectedVehiclesKey, "gfx.shadows.cab.range" - }; + }.Concat(GraphicsNewRenderer.Keys).ToList(); /// The last resort: no shadows, no reflections, no effects, no smoke, a softer picture. public static readonly GraphicsPreset Minimal = new( @@ -193,6 +295,10 @@ public sealed record GraphicsPreset( EnvMap: false, ReflectionsFidelity: 0, ReflectionsRate: 1 / 300.0, ExtraEffects: false, SplineFidelity: 1, DrawRange: 2000, DistanceFactor: 0.6, BufferScale: 70, SleeperDistance: 100, DrawRangeFactor: 1, ReflectedInstances: 250, ReflectedVehicles: 50, CabShadowsRange: 0, + NewRenderer: new GraphicsNewRenderer(LampShadows: "no", ContactShadows: "no", Occlusion: "no", OcclusionQuality: "3 4", + OcclusionLighting: false, CabProbe: "no", CabProbeInterval: 8, CabProbeReflections: false, ScreenReflections: "no", + RayTracing: false, Fog: "analytic", FogQuality: "low", Clouds: "no", CloudShadows: false, Grass: false, + GrassDistance: 30, GrassFade: 40, GrassDensity: 0.25, Precipitation: 16384, Bloom: false), Extras: new GraphicsExtras(MotionBlur: false, ChromaticAberration: false, Smoke: false, PythonUpdateMs: 800)); public static readonly GraphicsPreset Low = new( @@ -200,14 +306,22 @@ public sealed record GraphicsPreset( DynamicLights: 3, SmokeFidelity: 1, Shadows: true, ShadowMap: 1024, ShadowRange: 125, ShadowRank: 2, EnvMap: true, ReflectionsFidelity: 0, ReflectionsRate: 1 / 60.0, ExtraEffects: true, SplineFidelity: 1, DrawRange: 2000, DistanceFactor: 0.7, BufferScale: 80, - SleeperDistance: 150, DrawRangeFactor: 1.5, ReflectedInstances: 400, ReflectedVehicles: 100, CabShadowsRange: 0); + SleeperDistance: 150, DrawRangeFactor: 1.5, ReflectedInstances: 400, ReflectedVehicles: 100, CabShadowsRange: 0, + NewRenderer: new GraphicsNewRenderer(LampShadows: "low", ContactShadows: "no", Occlusion: "ssao", OcclusionQuality: "3 4", + OcclusionLighting: false, CabProbe: "no", CabProbeInterval: 8, CabProbeReflections: false, ScreenReflections: "low", + RayTracing: false, Fog: "volumetric", FogQuality: "low", Clouds: "low", CloudShadows: false, Grass: true, + GrassDistance: 40, GrassFade: 50, GrassDensity: 0.5, Precipitation: 32768, Bloom: true)); public static readonly GraphicsPreset Medium = new( "PresetMedium", TextureSize: 2048, CabTextureSize: 2048, Anisotropy: 4, Multisampling: 1, DynamicLights: 5, SmokeFidelity: 2, Shadows: true, ShadowMap: 1024, ShadowRange: 175, ShadowRank: 2, EnvMap: true, ReflectionsFidelity: 1, ReflectionsRate: 1 / 30.0, ExtraEffects: true, SplineFidelity: 2, DrawRange: 2250, DistanceFactor: 0.85, BufferScale: 90, - SleeperDistance: 200, DrawRangeFactor: 2, ReflectedInstances: 550, ReflectedVehicles: 150, CabShadowsRange: 20); + SleeperDistance: 200, DrawRangeFactor: 2, ReflectedInstances: 550, ReflectedVehicles: 150, CabShadowsRange: 20, + NewRenderer: new GraphicsNewRenderer(LampShadows: "low", ContactShadows: "no", Occlusion: "ssao", OcclusionQuality: "3 4", + OcclusionLighting: false, CabProbe: "low", CabProbeInterval: 8, CabProbeReflections: true, ScreenReflections: "low", + RayTracing: false, Fog: "volumetric", FogQuality: "low", Clouds: "low", CloudShadows: true, Grass: true, + GrassDistance: 50, GrassFade: 60, GrassDensity: 0.75, Precipitation: 49152, Bloom: true)); /// /// Where a player with no settings of their own starts. The simulator's own values (Globals.h), @@ -219,7 +333,11 @@ public sealed record GraphicsPreset( DynamicLights: 7, SmokeFidelity: 2, Shadows: true, ShadowMap: 2048, ShadowRange: 250, ShadowRank: 3, EnvMap: true, ReflectionsFidelity: 1, ReflectionsRate: 0.1, ExtraEffects: true, SplineFidelity: 2, DrawRange: 2500, DistanceFactor: 1, BufferScale: 100, - SleeperDistance: 250, DrawRangeFactor: 3, ReflectedInstances: 750, ReflectedVehicles: 250, CabShadowsRange: 40); + SleeperDistance: 250, DrawRangeFactor: 3, ReflectedInstances: 750, ReflectedVehicles: 250, CabShadowsRange: 40, + NewRenderer: new GraphicsNewRenderer(LampShadows: "medium", ContactShadows: "no", Occlusion: "gtao", OcclusionQuality: "3 4", + OcclusionLighting: true, CabProbe: "medium", CabProbeInterval: 4, CabProbeReflections: true, ScreenReflections: "medium", + RayTracing: false, Fog: "volumetric", FogQuality: "medium", Clouds: "medium", CloudShadows: true, Grass: true, + GrassDistance: 60, GrassFade: 70, GrassDensity: 1, Precipitation: 65536, Bloom: true)); /// For a strong card. A map of 4096 doubles the cascades, so the range here reaches 800 m. public static readonly GraphicsPreset VeryHigh = new( @@ -227,7 +345,11 @@ public sealed record GraphicsPreset( DynamicLights: 7, SmokeFidelity: 3, Shadows: true, ShadowMap: 4096, ShadowRange: 400, ShadowRank: 3, EnvMap: true, ReflectionsFidelity: 2, ReflectionsRate: 2, ExtraEffects: true, SplineFidelity: 3, DrawRange: 5000, DistanceFactor: 2, BufferScale: 150, - SleeperDistance: 2000, DrawRangeFactor: 3, ReflectedInstances: 1500, ReflectedVehicles: 1000, CabShadowsRange: 70); + SleeperDistance: 2000, DrawRangeFactor: 3, ReflectedInstances: 1500, ReflectedVehicles: 1000, CabShadowsRange: 70, + NewRenderer: new GraphicsNewRenderer(LampShadows: "high", ContactShadows: "medium", Occlusion: "gtao", OcclusionQuality: "4 6", + OcclusionLighting: true, CabProbe: "high", CabProbeInterval: 3, CabProbeReflections: true, ScreenReflections: "high", + RayTracing: false, Fog: "volumetric", FogQuality: "high", Clouds: "high", CloudShadows: true, Grass: true, + GrassDistance: 120, GrassFade: 140, GrassDensity: 1.5, Precipitation: 98304, Bloom: true)); /// /// Every value at the top of what the screen offers. Not the best picture throughout: a map @@ -240,6 +362,10 @@ public sealed record GraphicsPreset( EnvMap: true, ReflectionsFidelity: 2, ReflectionsRate: 60, ExtraEffects: true, SplineFidelity: 4, DrawRange: 10000, DistanceFactor: 3, BufferScale: 200, SleeperDistance: 5000, DrawRangeFactor: 3, ReflectedInstances: 3000, ReflectedVehicles: 2000, CabShadowsRange: 100, + NewRenderer: new GraphicsNewRenderer(LampShadows: "high", ContactShadows: "high", Occlusion: "gtao", OcclusionQuality: "6 8", + OcclusionLighting: true, CabProbe: "high", CabProbeInterval: 2, CabProbeReflections: true, ScreenReflections: "high", + RayTracing: true, Fog: "volumetric", FogQuality: "ultra", Clouds: "ultra", CloudShadows: true, Grass: true, + GrassDistance: 300, GrassFade: 350, GrassDensity: 2, Precipitation: 131072, Bloom: true), Extras: new GraphicsExtras(MotionBlur: true, ChromaticAberration: true, Smoke: true, PythonUpdateMs: 200), Top: new GraphicsTop(ShadowAngleLimit: 0.2, TargetFps: 0, ScreenWindowFps: 200, TextureRelease: 0, Screens: true, ScreenMipmaps: true, CompressTextures: false, SkipDecor: false, @@ -272,6 +398,7 @@ public sealed record GraphicsPreset( s.Extra[ReflectedInstancesKey] = ReflectedInstances.ToString(CultureInfo.InvariantCulture); s.Extra[ReflectedVehiclesKey] = ReflectedVehicles.ToString(CultureInfo.InvariantCulture); s.CabShadowsRange = CabShadowsRange; + NewRenderer.ApplyTo(s); Extras?.ApplyTo(s); Top?.ApplyTo(s); } @@ -292,6 +419,7 @@ public sealed record GraphicsPreset( Near(NumberOf(s, ReflectedInstancesKey), ReflectedInstances) && Near(NumberOf(s, ReflectedVehiclesKey), ReflectedVehicles) && s.CabShadowsRange == CabShadowsRange && + GraphicsNewRenderer.Of(s) == NewRenderer && (Extras is null || GraphicsExtras.Of(s) == Extras) && (Top is null || GraphicsTop.Of(s) == Top); @@ -349,7 +477,9 @@ public sealed record GraphicsGroup(IReadOnlyList Keys) GraphicsPreset.ReflectedInstancesKey, GraphicsPreset.ReflectedVehiclesKey }); - public static readonly IReadOnlyList All = new[] { DrawRange, Textures, Effects, Shadows, Reflections }; + public static readonly GraphicsGroup NewRenderer = new(GraphicsNewRenderer.Keys); + + public static readonly IReadOnlyList All = new[] { DrawRange, Textures, Effects, Shadows, Reflections, NewRenderer }; } /// What applying a level touched. diff --git a/StarterNG/Domain/Settings/SettingReach.cs b/StarterNG/Domain/Settings/SettingReach.cs index b00af23..3bc5fc5 100644 --- a/StarterNG/Domain/Settings/SettingReach.cs +++ b/StarterNG/Domain/Settings/SettingReach.cs @@ -17,7 +17,8 @@ public enum IdleReason /// The draw range is only stretched by the frame rate regulator, which is off. NeedsFrameRateTarget, /// fullscreenwindowed takes the monitor's own mode instead. - MonitorModeInstead + MonitorModeInstead, + VulkanOnly } /// What about the simulator to be started decides which keys it heeds. @@ -71,6 +72,11 @@ public static class SettingReach "scalespeculars", "gfx.drawrange.factor.max" }; + private static readonly HashSet VulkanRendererOnly = new(StringComparer.OrdinalIgnoreCase) + { + "gfx.dlss.preset", "gfx.framegeneration", "gfx.reflex", "gfx.raytracing", "gfx.raytracing.range", "gfx.vulkan.device" + }; + // application.cpp: fullscreenwindowed takes its size from the monitor and always // goes full screen. "width, height" is how the settings screen names that row. private static readonly HashSet OverriddenByMonitorMode = new(StringComparer.OrdinalIgnoreCase) @@ -95,6 +101,8 @@ public static class SettingReach return IdleReason.ModernRendererOnly; if (OpenGlRenderersOnly.Contains(key) && renderer == RendererKind.Experimental) return IdleReason.OpenGlRenderersOnly; + if (VulkanRendererOnly.Contains(key) && !IsVulkan(target.Renderer)) + return IdleReason.VulkanOnly; // on_focus_change pauses only a simulator that serves the network. if (string.Equals(key, "inactivepause", StringComparison.OrdinalIgnoreCase) && !target.NetworkServer) @@ -112,11 +120,13 @@ public static class SettingReach private static bool DrawRangeRegulated(SimulatorTarget target) => target.FpsAverage != 0 || (target.TargetFps ?? 0) != 0; - /// "full" and "default" are the modern renderer, "experimental" its own; every other word the legacy one. + /// "full", its other names and the Vulkan renderer are the modern kind, "experimental" its own; every other word the legacy one. public static RendererKind KindOf(string renderer) => renderer.ToLowerInvariant() switch { - "full" or "default" => RendererKind.Modern, + "full" or "default" or "opengl" or "gl46" or "vulkan" or "vulkan14" => RendererKind.Modern, "experimental" => RendererKind.Experimental, _ => RendererKind.Legacy }; + + private static bool IsVulkan(string renderer) => renderer.ToLowerInvariant() is "vulkan" or "vulkan14"; } diff --git a/StarterNG/Domain/Settings/SimulatorKeys.cs b/StarterNG/Domain/Settings/SimulatorKeys.cs index c391fe2..3193002 100644 --- a/StarterNG/Domain/Settings/SimulatorKeys.cs +++ b/StarterNG/Domain/Settings/SimulatorKeys.cs @@ -5,7 +5,7 @@ using System.Linq; namespace StarterNG.Domain.Settings; -public enum SimulatorKeyKind { Toggle, Number, Text } +public enum SimulatorKeyKind { Toggle, Number, Text, Choice } /// /// An eu07.ini key the simulator reads that needs no logic of the starter's own: @@ -24,13 +24,25 @@ public sealed record SimulatorKey( double Step = 1, string Unit = "", bool ZeroIsOff = false, - int Repeat = 1) + int Repeat = 1, + string[]? Words = null) { /// The label's translation key; the explanation's adds "Desc". public string LabelKey => "Ini." + Key; public string DescriptionKey => LabelKey + ".Desc"; + /// The translation key of a word of a choice; the empty one leaves the key out of the file. + public static string WordKey(string word) => + "IniWord." + (word.Length == 0 ? "same" : word.Replace(' ', '_')); + + /// The word of among those offered, or else the default: the row then shows that and leaves the file alone. + public string ParseChoice(string raw) + { + string word = string.Join(' ', raw.ToLowerInvariant().Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries)); + return Words is not null && Words.Contains(word) ? word : Default; + } + public static bool ParseToggle(string raw) => FirstToken(raw).ToLowerInvariant() is "yes" or "true" or "1" or "tak"; @@ -69,6 +81,8 @@ public static class SimulatorKeys public const string Physics = "physics"; public const string Advanced = "advanced"; + private const string NewRenderer = "GfxColNewRenderer"; + private static SimulatorKey Toggle(string key, bool value, string category, string section) => new(key, SimulatorKeyKind.Toggle, SimulatorKey.FormatToggle(value), category, section); @@ -81,6 +95,9 @@ public static class SimulatorKeys private static SimulatorKey Text(string key, string category, string section) => new(key, SimulatorKeyKind.Text, string.Empty, category, section); + private static SimulatorKey Choice(string key, string value, string category, string section, params string[] words) => + new(key, SimulatorKeyKind.Choice, value, category, section, Words: words); + public static SimulatorKey Named(string key) => All.First(k => k.Key == key); public static IReadOnlyList All { get; } = new[] @@ -114,6 +131,38 @@ public static class SimulatorKeys Toggle("newaircouplers", true, Graphics, "IniSectionRanges"), Toggle("smoothtraction", true, Graphics, "IniSectionRanges"), + // Read by the OpenGL 4.6 and Vulkan 1.4 simulators; the older ones pass them over. + Choice("gfx.antialiasing", "", Graphics, NewRenderer, "", "taa", "dlss", "dlaa"), + Choice("gfx.dlss.preset", "default", Graphics, NewRenderer, "default", "k", "j", "m", "l"), + Choice("gfx.framegeneration", "off", Graphics, NewRenderer, "off", "x2", "x3", "x4"), + Choice("gfx.reflex", "off", Graphics, NewRenderer, "off", "on", "boost"), + Toggle("gfx.raytracing", false, Graphics, NewRenderer), + Number("gfx.raytracing.range", 500, Graphics, NewRenderer, 10, 5000, 10, " m"), + Text("gfx.vulkan.device", Graphics, NewRenderer), + Toggle("gfx.lights.clustered", true, Graphics, NewRenderer), + Choice("gfx.lights.shadows", "medium", Graphics, NewRenderer, "no", "low", "medium", "high"), + Choice("gfx.contactshadows", "no", Graphics, NewRenderer, "no", "low", "medium", "high"), + Toggle("gfx.shadow.sdsm", false, Graphics, NewRenderer), + Choice("gfx.postfx.ao", "gtao", Graphics, NewRenderer, "no", "ssao", "gtao"), + Choice("gfx.postfx.ao.quality", "3 4", Graphics, NewRenderer, "3 4", "4 6", "6 8"), + Toggle("gfx.postfx.ao.lighting", true, Graphics, NewRenderer), + Choice("gfx.ssr", "medium", Graphics, NewRenderer, "no", "low", "medium", "high"), + Choice("gfx.cabprobe", "medium", Graphics, NewRenderer, "no", "low", "medium", "high"), + Number("gfx.cabprobe.interval", 4, Graphics, NewRenderer, 1, 16, 1), + Toggle("gfx.cabprobe.reflections", true, Graphics, NewRenderer), + Number("gfx.cabprobe.bounce", 1, Graphics, NewRenderer, 0, 4, 0.25), + Choice("gfx.fog", "volumetric", Graphics, NewRenderer, "analytic", "volumetric"), + Choice("gfx.fog.quality", "medium", Graphics, NewRenderer, "low", "medium", "high", "ultra"), + Choice("gfx.clouds", "medium", Graphics, NewRenderer, "no", "low", "medium", "high", "ultra"), + Toggle("gfx.clouds.shadows", true, Graphics, NewRenderer), + Toggle("gfx.grass", true, Graphics, NewRenderer), + Number("gfx.grass.distance", 60, Graphics, NewRenderer, 5, 300, 5, " m"), + Number("gfx.grass.fadedistance", 70, Graphics, NewRenderer, 10, 350, 10, " m"), + Number("gfx.grass.density", 1, Graphics, NewRenderer, 0, 2, 0.25, "×"), + Number("gfx.precipitation.particles", 65536, Graphics, NewRenderer, 0, 131072, 16384), + Toggle("gfx.postfx.bloom", true, Graphics, NewRenderer), + Toggle("gfx.postfx.autoexposure", true, Graphics, NewRenderer), + Number("sound.maxsources", 30, Sound, "IniSectionAudio", 8, 256, 8), Toggle("sound.asyncload", false, Sound, "IniSectionAudio"), diff --git a/StarterNG/Domain/Settings/SimulatorSettings.cs b/StarterNG/Domain/Settings/SimulatorSettings.cs index 5c22ad9..64183fc 100644 --- a/StarterNG/Domain/Settings/SimulatorSettings.cs +++ b/StarterNG/Domain/Settings/SimulatorSettings.cs @@ -157,16 +157,19 @@ public sealed class SimulatorSettings /// /// The gfxrenderer words the simulator tells apart (application.cpp): every /// other word - the "simpleshader" and "off" older starters offered among them - - /// gets the legacy renderer. + /// gets the legacy renderer. A simulator with the OpenGL 4.6 and Vulkan 1.4 + /// renderers runs everything but "vulkan" as the first, and "vulkan" too when + /// it was built without it or the card cannot run it. /// - public static readonly string[] RenderEngines = { "full", "legacy", "simple", "experimental" }; + public static readonly string[] RenderEngines = { "full", "legacy", "simple", "experimental", "vulkan" }; /// Where stands among , as the simulator reads it. public static int RenderEngineOf(string word) => word.Trim().ToLowerInvariant() switch { - "full" or "default" => 0, + "full" or "default" or "opengl" or "gl46" => 0, "simple" => 2, "experimental" => 3, + "vulkan" or "vulkan14" => 4, _ => 1 }; diff --git a/StarterNG/Infrastructure/Settings/SettingsSerializer.cs b/StarterNG/Infrastructure/Settings/SettingsSerializer.cs index 4f54d74..3b0fc96 100644 --- a/StarterNG/Infrastructure/Settings/SettingsSerializer.cs +++ b/StarterNG/Infrastructure/Settings/SettingsSerializer.cs @@ -69,7 +69,7 @@ public sealed class SettingsSerializer s.RenderEngine = SimulatorSettings.RenderEngineOf(c.GetString("gfxrenderer", "full")); s.Width = c.GetInt("width", 1280); s.Height = c.GetInt("height", 720); - s.BufferScalePercent = Clamp(c.GetInt("starter.bufferscale", 100), 50, 200); + s.BufferScalePercent = ReadBufferScale(c); s.MaxTextureSize = c.GetInt("maxtexturesize", 4096); s.MaxCabTextureSize = c.GetInt("maxcabtexturesize", 4096); s.TextureFiltering = Math.Max(1, c.GetDouble("anisotropicfiltering", 8)); @@ -246,8 +246,10 @@ public sealed class SettingsSerializer c.SetInt("starter.bufferscale", s.BufferScalePercent); int fbw = (int)Math.Round(s.Width * s.BufferScalePercent / 100.0); int fbh = (int)Math.Round(s.Height * s.BufferScalePercent / 100.0); + // The size for the simulators before gfx.buffer.percent, which pass that one over and the other way round. c.SetInt("gfx.framebuffer.width", fbw); c.SetInt("gfx.framebuffer.height", fbh); + c.SetInt("gfx.buffer.percent", s.BufferScalePercent); c.SetInt("maxtexturesize", s.MaxTextureSize); c.SetInt("maxcabtexturesize", s.MaxCabTextureSize); c.SetDouble("anisotropicfiltering", s.TextureFiltering); @@ -334,6 +336,16 @@ public sealed class SettingsSerializer c.Set(key.Key, value.Trim()); } + // What the simulator goes by: up to 4 a part of the window, above it percents. + private static int ReadBufferScale(ConfigFile c) + { + if (!c.Has("gfx.buffer.percent")) + return Clamp(c.GetInt("starter.bufferscale", 100), 25, 200); + + double value = c.GetDouble("gfx.buffer.percent", 100); + return Clamp((int)Math.Round(value > 4 ? value : value * 100), 25, 200); + } + private static int Clamp(int v, int lo, int hi) => v < lo ? lo : (v > hi ? hi : v); private static int SensitivityStep(double mouseScale) => diff --git a/StarterNG/Views/SettingTexts.cs b/StarterNG/Views/SettingTexts.cs index 63edfc4..018a325 100644 --- a/StarterNG/Views/SettingTexts.cs +++ b/StarterNG/Views/SettingTexts.cs @@ -103,6 +103,22 @@ internal static class SettingTexts Add("Ini." + GraphicsPreset.ReflectedVehiclesKey, Whole(p.ReflectedVehicles) + " m"); Add("CabShadowsRange", p.CabShadowsRange + " m"); + var renderer = p.NewRenderer; + string Word(string word) => App.Loc[SimulatorKey.WordKey(word)]; + lines.Add(App.Loc["GfxColNewRenderer"] + ":"); + Add("Ini.gfx.lights.shadows", Word(renderer.LampShadows)); + Add("Ini.gfx.contactshadows", Word(renderer.ContactShadows)); + Add("Ini.gfx.postfx.ao", Word(renderer.Occlusion)); + Add("Ini.gfx.postfx.ao.quality", Word(renderer.OcclusionQuality)); + Add("Ini.gfx.cabprobe", Word(renderer.CabProbe)); + Add("Ini.gfx.ssr", Word(renderer.ScreenReflections)); + Add("Ini.gfx.raytracing", YesNo(renderer.RayTracing)); + Add("Ini.gfx.fog", Word(renderer.Fog)); + Add("Ini.gfx.fog.quality", Word(renderer.FogQuality)); + Add("Ini.gfx.clouds", Word(renderer.Clouds)); + Add("Ini.gfx.grass", renderer.Grass ? Whole(renderer.GrassDistance) + " m" : App.Loc["No"]); + Add("Ini.gfx.postfx.bloom", YesNo(renderer.Bloom)); + if (p.Extras is { } extras) { Add("MotionBlur", YesNo(extras.MotionBlur)); diff --git a/StarterNG/Views/Settings.axaml b/StarterNG/Views/Settings.axaml index 025bdc6..ef415fe 100644 --- a/StarterNG/Views/Settings.axaml +++ b/StarterNG/Views/Settings.axaml @@ -191,6 +191,7 @@ + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/StarterNG/Views/Settings.axaml.cs b/StarterNG/Views/Settings.axaml.cs index 2523910..d4b0c70 100644 --- a/StarterNG/Views/Settings.axaml.cs +++ b/StarterNG/Views/Settings.axaml.cs @@ -516,7 +516,7 @@ public partial class Settings : UserControl, ISettingsCapture { (DrawRangeSection, GraphicsGroup.DrawRange), (TexturesSection, GraphicsGroup.Textures), (EffectsSection, GraphicsGroup.Effects), (ShadowsSection, GraphicsGroup.Shadows), - (ReflectionsSection, GraphicsGroup.Reflections) + (ReflectionsSection, GraphicsGroup.Reflections), (NewRendererSection, GraphicsGroup.NewRenderer) }) { var levels = new ComboBox(); @@ -1098,6 +1098,7 @@ public partial class Settings : UserControl, ISettingsCapture IdleReason.NeedsNetworkServer => App.Loc["IdleNeedsNetworkServer"], IdleReason.NeedsFrameRateTarget => App.Loc["IdleNeedsFrameRateTarget"], IdleReason.MonitorModeInstead => App.Loc["IdleMonitorMode"], + IdleReason.VulkanOnly => App.Loc["IdleVulkanOnly"], _ => null }; diff --git a/StarterNG/Views/SimulatorKeyRows.cs b/StarterNG/Views/SimulatorKeyRows.cs index a59fea5..a6309b0 100644 --- a/StarterNG/Views/SimulatorKeyRows.cs +++ b/StarterNG/Views/SimulatorKeyRows.cs @@ -77,6 +77,7 @@ internal sealed class SimulatorKeyRows TickFrequency = key.Step, IsSnapToTickEnabled = true }, + SimulatorKeyKind.Choice => ChoiceBox(key), _ => new TextBox { MinWidth = 200 } }; @@ -102,11 +103,23 @@ internal sealed class SimulatorKeyRows case TextBox text: text.TextChanged += (_, _) => OnEdited(row); break; + case ComboBox box: + box.SelectionChanged += (_, _) => OnEdited(row); + break; } return setting; } + private static ComboBox ChoiceBox(SimulatorKey key) + { + var box = new ComboBox(); + box.Classes.Add("Localized"); + foreach (string word in key.Words ?? Array.Empty()) + box.Items.Add(new ComboBoxItem { Tag = word }); + return box; + } + /// /// Only a value other than the one shown counts: a text box reports the text it /// was given after the fact, and a slider reports its own clamping. @@ -159,6 +172,11 @@ internal sealed class SimulatorKeyRows row.Setting.Label = label; text.PlaceholderText = App.Loc["IniDefault"]; break; + case ComboBox choice: + row.Setting.Label = label; + foreach (var item in choice.Items.OfType()) + item.Content = App.Loc[SimulatorKey.WordKey(item.Tag as string ?? string.Empty)]; + break; default: row.Setting.Label = label; UpdateValueText(row); @@ -224,6 +242,10 @@ internal sealed class SimulatorKeyRows case TextBox text: text.Text = raw.Trim(); break; + case ComboBox choice: + string word = row.Key.ParseChoice(raw); + choice.SelectedItem = choice.Items.OfType().FirstOrDefault(item => item.Tag as string == word); + break; } } @@ -232,6 +254,7 @@ internal sealed class SimulatorKeyRows CheckBox box => SimulatorKey.FormatToggle(box.IsChecked == true), Slider slider => row.Key.FormatNumber(slider.Value), TextBox text => (text.Text ?? string.Empty).Trim(), + ComboBox choice => (choice.SelectedItem as ComboBoxItem)?.Tag as string ?? row.Key.Default, _ => string.Empty }; diff --git a/StarterNG/startercfg/lang/cz.xml b/StarterNG/startercfg/lang/cz.xml index 92f7e7a..4ffb30e 100644 --- a/StarterNG/startercfg/lang/cz.xml +++ b/StarterNG/startercfg/lang/cz.xml @@ -407,7 +407,7 @@ Grafika Vykreslovací engine Plný - Plný: OpenGL 3.3 se stíny a odrazy. Starý a Zjednodušený: pro slabé karty. Experimentální: Vulkan, Direct3D 12, ve vývoji. + Plný: OpenGL se stíny a odrazy, v novém simulátoru OpenGL 4.6. Starý, Zjednodušený, Experimentální: starší verze simulátoru, nová spustí Plný. Vulkan 1.4: když ho exe nemá nebo ho karta nezvládne, nová verze spustí OpenGL a starší Starý. Starý Zjednodušený Experimentální @@ -865,4 +865,64 @@ Ve skladu: {0} Vyberte soupravu s vozidly Úroveň jen pro tuto část grafiky + Vulkan 1.4 + Funguje jen s Vulkanem. + Nový renderer (OpenGL 4.6, Vulkan) + Vyhlazování TAA / DLSS + Předvolba DLSS + Generování snímků + NVIDIA Reflex + Odrazy ray tracing + Dosah ray tracingu + Grafická karta (Vulkan) + Odrazy v prostoru obrazovky (SSR) + Okolní zastínění + Kvalita zastínění + Zastínění v okolním světle + Klastrové osvětlení + Stíny lamp a reflektorů + Kontaktní stíny + Kaskády stínů podle scény (SDSM) + Sonda kabiny + Obnovení sondy každých N snímků + Kabina odráží sebe + Odražené světlo lampy v kabině + Mlha + Kvalita mlhy + Objemová oblaka + Stíny oblaků na zemi + Tráva + Dosah trávy + Tráva mizí do + Hustota trávy + Srážky (částice) + Záře světlých míst (bloom) + Automatická expozice + Jako MSAA výše + Vypnuto + Vypnuto + Nízká + Střední + Vysoká + Ultra + Zapnuto + Zapnuto + boost + SSAO + GTAO + Objemová + Analytická + Automaticky + K: nejlepší kvalita + J: méně rozmazání + M: pro režim Výkon + L: pro Ultra výkon + x2 + x3 + x4 + TAA + DLSS (škálování) + DLAA (nativní rozlišení) + Výchozí (3×4) + Vysoká (4×6) + Ultra (6×8) diff --git a/StarterNG/startercfg/lang/en.xml b/StarterNG/startercfg/lang/en.xml index b02cf92..b43be8e 100644 --- a/StarterNG/startercfg/lang/en.xml +++ b/StarterNG/startercfg/lang/en.xml @@ -413,7 +413,7 @@ Graphics Render engine - Full: OpenGL 3.3 with shadows and reflections. Old and Simplified: for weak cards. Experimental: Vulkan, Direct3D 12, in development. + Full: OpenGL with shadows and reflections, OpenGL 4.6 in the new simulator. Old, Simplified, Experimental: older simulator versions, the new one starts Full. Vulkan 1.4: if the exe lacks it or the card cannot run it, the new version starts OpenGL and an older one Old. Full Old Simplified @@ -1097,4 +1097,83 @@ In the warehouse: {0} Pick a consist with vehicles Level for this part of the graphics only + Vulkan 1.4 + Works with Vulkan only. + New renderer (OpenGL 4.6, Vulkan) + TAA / DLSS anti-aliasing + Replaces MSAA. TAA smooths the whole image at the cost of slight blur. DLSS and DLAA need Vulkan and an NVIDIA RTX card. + DLSS preset + The model DLSS rebuilds the image with. The DLSS mode follows the buffer scale: about 67% Quality, 50% Performance, 33% Ultra Performance. + Frame generation + Extra frames between the drawn ones. With DLSS or DLAA only. x3 and x4 need an RTX 50 card. + NVIDIA Reflex + Less delay between the controls and the image. + Ray traced reflections + Fills in reflections where SSR finds nothing. The card needs ray query. + Ray tracing range + Graphics card (Vulkan) + A card's number from the list in the log, or part of its name, like RTX. Empty: chosen automatically. + Screen space reflections (SSR) + Reflections of what is on screen, on wet and smooth surfaces. + Ambient occlusion + Darkens corners and places where surfaces meet. GTAO is more accurate than SSAO. + Occlusion quality + Occlusion in ambient light + GTAO only. Darkens ambient light and reflections, sun and lamps stay bright. Off: multiplies the finished image, cheaper. + Clustered lighting + Vehicle lights and street lamps with no limit on their number. Needed for scenery lamps and the cab lamp. + Lamp and headlight shadows + Shadows cast by the cab lamp, street lamps and vehicle headlights. + Contact shadows + Small shadows where objects touch, which the shadow map misses. + Shadow cascades fitted to the scene (SDSM) + Spreads the sun shadow resolution over what is actually in view. + Cab probe + Light bounced off the cab walls and reflections of the interior in the windows. + Refresh the probe every N frames + Cab reflects itself + The windows show the cab interior instead of the sky where SSR finds nothing. + Cab lamp light bounce + How much of the lamp's light reflected off the walls lights the cab again. Works with the cab probe. + Fog + Volumetric: with sun rays, cloud shadows and lamp light. Analytic: simpler and cheaper. + Fog quality + Volumetric clouds + Cloud shadows on the ground + Grass + Grass range + Grass fades out at + Grass density + Precipitation (particles) + Number of raindrops and snowflakes in the heaviest precipitation. + Glow of bright areas (bloom) + Automatic exposure + Image brightness adapts to the scene, for instance entering a tunnel. + As MSAA above + Off + Off + Low + Medium + High + Ultra + On + On + boost + SSAO + GTAO + Volumetric + Analytic + Automatic + K: best quality + J: less smearing + M: for Performance mode + L: for Ultra Performance + x2 + x3 + x4 + TAA + DLSS (upscaling) + DLAA (native resolution) + Default (3×4) + High (4×6) + Ultra (6×8) diff --git a/StarterNG/startercfg/lang/hu.xml b/StarterNG/startercfg/lang/hu.xml index ae30b44..8fedefa 100644 --- a/StarterNG/startercfg/lang/hu.xml +++ b/StarterNG/startercfg/lang/hu.xml @@ -407,7 +407,7 @@ Grafika Megjelenítő motor Teljes - Teljes: OpenGL 3.3 árnyékokkal és tükröződéssel. Régi és Egyszerűsített: gyenge kártyákhoz. Kísérleti: Vulkan, Direct3D 12, fejlesztés alatt. + Teljes: OpenGL árnyékokkal és tükröződéssel, az új szimulátorban OpenGL 4.6. Régi, Egyszerűsített, Kísérleti: a szimulátor régebbi verziói, az új a Teljeset indítja. Vulkan 1.4: ha az exe nem tartalmazza vagy a kártya nem bírja, az új verzió OpenGL-t, a régebbi a Régit indítja. Régi Egyszerűsített Kísérleti @@ -865,4 +865,64 @@ A raktárban: {0} Válassz járműveket tartalmazó szerelvényt Csak a grafika ezen részének szintje + Vulkan 1.4 + Csak Vulkannal működik. + Új renderelő (OpenGL 4.6, Vulkan) + TAA / DLSS élsimítás + DLSS előbeállítás + Képkocka-generálás + NVIDIA Reflex + Sugárkövetéses tükröződés + Sugárkövetés hatótávja + Videokártya (Vulkan) + Képernyőtéri tükröződés (SSR) + Környezeti kitakarás + Kitakarás minősége + Kitakarás a környezeti fényben + Klaszteres megvilágítás + Lámpák és fényszórók árnyéka + Kontaktárnyékok + Jelenethez igazított árnyékkaszkádok (SDSM) + Fülkeszonda + Szonda frissítése N képkockánként + A fülke önmagát tükrözi + A fülkelámpa visszavert fénye + Köd + Köd minősége + Térfogati felhők + Felhők árnyéka a talajon + Fű + Fű hatótávja + A fű eltűnik eddig + Fű sűrűsége + Csapadék (részecskék) + Világos részek ragyogása (bloom) + Automatikus expozíció + Mint a fenti MSAA + Ki + Ki + Alacsony + Közepes + Magas + Ultra + Be + Be + boost + SSAO + GTAO + Térfogati + Analitikus + Automatikus + K: legjobb minőség + J: kevesebb elmosódás + M: Teljesítmény módhoz + L: Ultra teljesítményhez + x2 + x3 + x4 + TAA + DLSS (felskálázás) + DLAA (natív felbontás) + Alapértelmezett (3×4) + Magas (4×6) + Ultra (6×8) diff --git a/StarterNG/startercfg/lang/pl.xml b/StarterNG/startercfg/lang/pl.xml index 8570281..2e0d031 100644 --- a/StarterNG/startercfg/lang/pl.xml +++ b/StarterNG/startercfg/lang/pl.xml @@ -413,7 +413,7 @@ Grafika Silnik renderowania - Pełny: OpenGL 3.3 z cieniami i odbiciami. Stary i Uproszczony: dla słabych kart. Eksperymentalny: Vulkan, Direct3D 12, w rozwoju. + Pełny: OpenGL z cieniami i odbiciami, w nowym symulatorze OpenGL 4.6. Stary, Uproszczony, Eksperymentalny: starsze wersje symulatora, nowa włączy Pełny. Vulkan 1.4: gdy exe go nie ma albo karta nie da rady, nowa wersja włączy OpenGL, a starsza Stary. Pełny Stary Uproszczony @@ -919,4 +919,83 @@ W magazynie: {0} Wybierz skład z pojazdami Poziom tylko tej części grafiki + Vulkan 1.4 + Działa tylko z Vulkanem. + Nowy renderer (OpenGL 4.6, Vulkan) + Wygładzanie TAA / DLSS + Zastępuje MSAA. TAA wygładza cały obraz kosztem lekkiego rozmycia. DLSS i DLAA tylko z Vulkanem na kartach NVIDIA RTX. + Preset DLSS + Model, którym DLSS odtwarza obraz. Tryb DLSS wynika ze skali bufora: ok. 67% Jakość, 50% Wydajność, 33% Ultra wydajność. + Generowanie klatek + Dodatkowe klatki między rysowanymi. Tylko z DLSS lub DLAA. x3 i x4 tylko na kartach RTX 50. + NVIDIA Reflex + Mniejsze opóźnienie między sterowaniem a obrazem. + Odbicia ray tracing + Uzupełnia odbicia tam, gdzie SSR nic nie znajduje. Karta musi mieć ray query. + Zasięg ray tracingu + Karta graficzna (Vulkan) + Numer karty z listy w logu albo fragment nazwy, np. RTX. Puste: wybór automatyczny. + Odbicia w przestrzeni ekranu (SSR) + Odbicia tego, co widać na ekranie, na mokrych i gładkich powierzchniach. + Okluzja otoczenia + Przyciemnia zakamarki i styki powierzchni. GTAO jest dokładniejsze od SSAO. + Jakość okluzji + Okluzja w świetle otoczenia + Tylko z GTAO. Przyciemnia światło otoczenia i odbicia, a słońce i lampy zostają jasne. Wyłączone: mnoży gotowy obraz, taniej. + Oświetlenie klastrowe + Światła pojazdów i latarnie bez limitu liczby. Potrzebne dla lamp scenerii i lampy w kabinie. + Cienie lamp i reflektorów + Cienie rzucane przez lampę kabiny, latarnie i reflektory pojazdów. + Cienie kontaktowe + Drobne cienie w miejscach styku obiektów, których mapa cieni nie łapie. + Kaskady cieni dopasowane do sceny (SDSM) + Rozkłada rozdzielczość cieni słońca na to, co faktycznie widać. + Próbka kabiny + Światło odbite od ścian kabiny i odbicia wnętrza w szybach. + Odświeżanie próbki co N klatek + Kabina odbija siebie + W szybach widać wnętrze kabiny zamiast nieba tam, gdzie SSR nic nie znajduje. + Odbite światło lampy w kabinie + Ile światła lampy odbitego od ścian oświetla kabinę ponownie. Działa z próbką kabiny. + Mgła + Wolumetryczna: z promieniami słońca, cieniami chmur i światłem lamp. Analityczna: prostsza i tańsza. + Jakość mgły + Chmury wolumetryczne + Cienie chmur na ziemi + Trawa + Zasięg trawy + Trawa zanika do + Gęstość trawy + Opady (cząsteczki) + Liczba kropli deszczu i płatków śniegu przy największych opadach. + Poświata jasnych miejsc (bloom) + Automatyczna ekspozycja + Jasność obrazu dopasowuje się do sceny, np. przy wjeździe do tunelu. + Jak MSAA wyżej + Wyłączone + Wyłączone + Niskie + Średnie + Wysokie + Ultra + Włączony + Włączony + boost + SSAO + GTAO + Wolumetryczna + Analityczna + Automatyczny + K: najlepsza jakość + J: mniej smużenia + M: pod tryb Wydajność + L: pod Ultra wydajność + x2 + x3 + x4 + TAA + DLSS (skalowanie) + DLAA (natywna rozdzielczość) + Domyślna (3×4) + Wysoka (4×6) + Ultra (6×8) diff --git a/StarterNG/startercfg/lang/ru.xml b/StarterNG/startercfg/lang/ru.xml index d27fc69..208cb43 100644 --- a/StarterNG/startercfg/lang/ru.xml +++ b/StarterNG/startercfg/lang/ru.xml @@ -407,7 +407,7 @@ Графика Движок отрисовки Полный - Полный: OpenGL 3.3 с тенями и отражениями. Старый и Упрощённый: для слабых карт. Экспериментальный: Vulkan, Direct3D 12, в разработке. + Полный: OpenGL с тенями и отражениями, в новом симуляторе OpenGL 4.6. Старый, Упрощённый, Экспериментальный: старые версии симулятора, новая запустит Полный. Vulkan 1.4: если его нет в exe или карта не справится, новая версия запустит OpenGL, а старая Старый. Старый Упрощённый Экспериментальный @@ -865,4 +865,64 @@ На складе: {0} Выберите состав с транспортом Уровень только для этой части графики + Vulkan 1.4 + Работает только с Vulkan. + Новый рендерер (OpenGL 4.6, Vulkan) + Сглаживание TAA / DLSS + Пресет DLSS + Генерация кадров + NVIDIA Reflex + Отражения с трассировкой лучей + Дальность трассировки лучей + Видеокарта (Vulkan) + Отражения в экранном пространстве (SSR) + Фоновое затенение + Качество затенения + Затенение в фоновом свете + Кластерное освещение + Тени ламп и прожекторов + Контактные тени + Каскады теней по сцене (SDSM) + Зонд кабины + Обновление зонда каждые N кадров + Кабина отражает себя + Отражённый свет лампы в кабине + Туман + Качество тумана + Объёмные облака + Тени облаков на земле + Трава + Дальность травы + Трава исчезает на + Густота травы + Осадки (частицы) + Свечение ярких мест (bloom) + Автоматическая экспозиция + Как MSAA выше + Выкл. + Выкл. + Низкое + Среднее + Высокое + Ультра + Вкл. + Вкл. + boost + SSAO + GTAO + Объёмный + Аналитический + Автоматически + K: лучшее качество + J: меньше размытия + M: для режима Производительность + L: для Ультра производительность + x2 + x3 + x4 + TAA + DLSS (масштабирование) + DLAA (родное разрешение) + По умолчанию (3×4) + Высокое (4×6) + Ультра (6×8)