Introduce a RenderQuality enum (Low/Medium/High) and allow users to switch quality at runtime via keyboard shortcuts. The fractal renderer now adapts its internal resolution, particle counts, and computational workload based on selected quality and real-time performance, aiming to maintain a target FPS. HUD and window title display current quality and FPS. Renderer buffers and limits are dynamically resized on window or quality changes for improved performance and flexibility.
278 lines
8.8 KiB
C#
278 lines
8.8 KiB
C#
using System;
|
|
using FractalVisualizer.Audio;
|
|
using FractalVisualizer.Models;
|
|
using FractalVisualizer.Rendering;
|
|
using FractalVisualizer.UI;
|
|
using Microsoft.Xna.Framework;
|
|
using Microsoft.Xna.Framework.Graphics;
|
|
using Microsoft.Xna.Framework.Input;
|
|
|
|
namespace FractalVisualizer;
|
|
|
|
public class Game1 : Game
|
|
{
|
|
private readonly GraphicsDeviceManager _graphics;
|
|
|
|
private SpriteBatch? _spriteBatch;
|
|
private Texture2D? _pixel;
|
|
private HudBitmapFont? _hudFont;
|
|
|
|
private readonly AudioCapture _audioCapture;
|
|
private readonly AudioAnalyzer _audioAnalyzer;
|
|
private FractalRenderer? _fractalRenderer;
|
|
|
|
private KeyboardState _previousKeyboardState;
|
|
private AudioInputMode _currentInputMode;
|
|
private AudioFeatures _latestFeatures;
|
|
private bool _isPaused;
|
|
private bool _showHud = true;
|
|
private int _fractalPresetIndex;
|
|
private int _fps;
|
|
private int _frameCounter;
|
|
private TimeSpan _fpsElapsed;
|
|
private const int TargetFps = 120;
|
|
private RenderQuality _renderQuality = RenderQuality.Medium;
|
|
private readonly (int Width, int Height)[] _windowedResolutions =
|
|
[
|
|
(1280, 720),
|
|
(1600, 900),
|
|
(1920, 1080)
|
|
];
|
|
private int _resolutionIndex = 0;
|
|
|
|
public Game1()
|
|
{
|
|
_graphics = new GraphicsDeviceManager(this);
|
|
_graphics.SynchronizeWithVerticalRetrace = false;
|
|
_graphics.PreferredBackBufferWidth = 1280;
|
|
_graphics.PreferredBackBufferHeight = 720;
|
|
Content.RootDirectory = "Content";
|
|
IsMouseVisible = true;
|
|
Window.AllowUserResizing = true;
|
|
IsFixedTimeStep = true;
|
|
TargetElapsedTime = TimeSpan.FromSeconds(1.0 / 120.0);
|
|
|
|
_audioCapture = new AudioCapture();
|
|
_audioAnalyzer = new AudioAnalyzer(_audioCapture, fftSize: 2048, spectrumBins: 64);
|
|
_currentInputMode = AudioInputMode.Microphone;
|
|
_latestFeatures = AudioFeatures.CreateEmpty(64);
|
|
}
|
|
|
|
protected override void Initialize()
|
|
{
|
|
_audioCapture.Start();
|
|
_audioAnalyzer.Start();
|
|
Window.Title = "Fractal Visualizer";
|
|
base.Initialize();
|
|
}
|
|
|
|
protected override void LoadContent()
|
|
{
|
|
_spriteBatch = new SpriteBatch(GraphicsDevice);
|
|
_fractalRenderer = new FractalRenderer(GraphicsDevice);
|
|
_fractalRenderer.SetQuality(_renderQuality);
|
|
_pixel = new Texture2D(GraphicsDevice, 1, 1, false, SurfaceFormat.Color);
|
|
_pixel.SetData([Color.White]);
|
|
_hudFont = new HudBitmapFont();
|
|
}
|
|
|
|
protected override void Update(GameTime gameTime)
|
|
{
|
|
KeyboardState keyboard = Keyboard.GetState();
|
|
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
|
|
{
|
|
Exit();
|
|
return;
|
|
}
|
|
|
|
if (IsPressed(keyboard, Keys.Escape))
|
|
{
|
|
if (_graphics.IsFullScreen)
|
|
{
|
|
_graphics.ToggleFullScreen();
|
|
}
|
|
else
|
|
{
|
|
Exit();
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (IsPressed(keyboard, Keys.F11))
|
|
{
|
|
ToggleFullScreen();
|
|
}
|
|
|
|
if (IsPressed(keyboard, Keys.F))
|
|
{
|
|
ToggleFullScreen();
|
|
}
|
|
|
|
if (IsPressed(keyboard, Keys.R))
|
|
{
|
|
CycleWindowResolution();
|
|
}
|
|
|
|
if (IsPressed(keyboard, Keys.D1))
|
|
{
|
|
SetRenderQuality(RenderQuality.Low);
|
|
}
|
|
|
|
if (IsPressed(keyboard, Keys.D2))
|
|
{
|
|
SetRenderQuality(RenderQuality.Medium);
|
|
}
|
|
|
|
if (IsPressed(keyboard, Keys.D3))
|
|
{
|
|
SetRenderQuality(RenderQuality.High);
|
|
}
|
|
|
|
if (IsPressed(keyboard, Keys.Space))
|
|
{
|
|
_isPaused = !_isPaused;
|
|
}
|
|
|
|
if (IsPressed(keyboard, Keys.Tab))
|
|
{
|
|
_showHud = !_showHud;
|
|
}
|
|
|
|
if (IsPressed(keyboard, Keys.Left))
|
|
{
|
|
_fractalPresetIndex = Math.Max(0, _fractalPresetIndex - 1);
|
|
}
|
|
|
|
if (IsPressed(keyboard, Keys.Right))
|
|
{
|
|
_fractalPresetIndex = Math.Min(19, _fractalPresetIndex + 1);
|
|
}
|
|
|
|
if (IsPressed(keyboard, Keys.M))
|
|
{
|
|
_currentInputMode = _currentInputMode == AudioInputMode.Microphone
|
|
? AudioInputMode.SystemOutput
|
|
: AudioInputMode.Microphone;
|
|
_audioCapture.SwitchMode(_currentInputMode);
|
|
}
|
|
|
|
_latestFeatures = _audioAnalyzer.GetLatestFeatures();
|
|
_fpsElapsed += gameTime.ElapsedGameTime;
|
|
_frameCounter++;
|
|
if (_fpsElapsed >= TimeSpan.FromSeconds(1))
|
|
{
|
|
_fps = _frameCounter;
|
|
_frameCounter = 0;
|
|
_fpsElapsed -= TimeSpan.FromSeconds(1);
|
|
}
|
|
|
|
Window.Title = $"Fractal Visualizer | {GetPresetName(_fractalPresetIndex)} | {_renderQuality} | Mode: {_currentInputMode} | FPS:{_fps}";
|
|
_previousKeyboardState = keyboard;
|
|
base.Update(gameTime);
|
|
}
|
|
|
|
protected override void Draw(GameTime gameTime)
|
|
{
|
|
GraphicsDevice.Clear(Color.Black);
|
|
|
|
if (_fractalRenderer is not null)
|
|
{
|
|
_fractalRenderer.UpdatePerformanceHint(_fps, TargetFps);
|
|
float renderTime = _isPaused ? _latestFeatures.TimeSeconds : (float)gameTime.TotalGameTime.TotalSeconds;
|
|
_fractalRenderer.Draw(_latestFeatures, renderTime, _fractalPresetIndex);
|
|
}
|
|
|
|
if (_showHud && _spriteBatch is not null && _hudFont is not null && _pixel is not null)
|
|
{
|
|
_spriteBatch.Begin(samplerState: SamplerState.PointClamp, blendState: BlendState.AlphaBlend);
|
|
string modeText = _currentInputMode == AudioInputMode.Microphone ? "MIC" : "SYS";
|
|
_hudFont.DrawText(_spriteBatch, _pixel, "MODE " + modeText + " M SWITCH", 12, 10, 2, Color.White);
|
|
_hudFont.DrawText(_spriteBatch, _pixel, "PRESET " + GetPresetName(_fractalPresetIndex) + " LEFT RIGHT", 12, 30, 2, Color.White);
|
|
_hudFont.DrawText(_spriteBatch, _pixel, "LEVEL " + _latestFeatures.Level.ToString("0.000"), 12, 50, 2, Color.White);
|
|
_hudFont.DrawText(_spriteBatch, _pixel, "BASS " + _latestFeatures.Bass.ToString("0.000"), 12, 70, 2, Color.White);
|
|
_hudFont.DrawText(_spriteBatch, _pixel, "MID " + _latestFeatures.Mid.ToString("0.000"), 12, 90, 2, Color.White);
|
|
_hudFont.DrawText(_spriteBatch, _pixel, "HIGH " + _latestFeatures.High.ToString("0.000"), 12, 110, 2, Color.White);
|
|
_hudFont.DrawText(_spriteBatch, _pixel, "BEAT " + _latestFeatures.Beat.ToString("0.000"), 12, 130, 2, Color.White);
|
|
_hudFont.DrawText(_spriteBatch, _pixel, "PAUSED " + (_isPaused ? "YES" : "NO"), 12, 150, 2, Color.White);
|
|
_hudFont.DrawText(_spriteBatch, _pixel, "FPS " + _fps, 12, 170, 2, Color.White);
|
|
_hudFont.DrawText(_spriteBatch, _pixel, "QUALITY " + _renderQuality + " 1 2 3", 12, 190, 2, Color.White);
|
|
_hudFont.DrawText(_spriteBatch, _pixel, "F OR F11 FULLSCREEN R RESIZE", 12, 210, 2, Color.White);
|
|
_spriteBatch.End();
|
|
}
|
|
|
|
base.Draw(gameTime);
|
|
}
|
|
|
|
protected override void Dispose(bool disposing)
|
|
{
|
|
if (disposing)
|
|
{
|
|
_audioAnalyzer.Dispose();
|
|
_audioCapture.Dispose();
|
|
_fractalRenderer?.Dispose();
|
|
_pixel?.Dispose();
|
|
_spriteBatch?.Dispose();
|
|
}
|
|
|
|
base.Dispose(disposing);
|
|
}
|
|
|
|
private bool IsPressed(KeyboardState keyboardState, Keys key)
|
|
{
|
|
return keyboardState.IsKeyDown(key) && _previousKeyboardState.IsKeyUp(key);
|
|
}
|
|
|
|
private static string GetPresetName(int presetIndex)
|
|
{
|
|
return presetIndex switch
|
|
{
|
|
0 => "JULIA",
|
|
1 => "MANDELBULB",
|
|
2 => "KALEIDO",
|
|
3 => "FLAME",
|
|
4 => "PLASMA",
|
|
5 => "NEBULA",
|
|
6 => "FLAMEGRILL",
|
|
7 => "NOTES",
|
|
8 => "FIREWORKS",
|
|
9 => "EQCITY",
|
|
10 => "METABALL",
|
|
11 => "GALAXY",
|
|
12 => "AURORA",
|
|
13 => "VORTEX",
|
|
14 => "LASERGRID",
|
|
15 => "RAINRIPPLE",
|
|
16 => "INKSMOKE",
|
|
17 => "MOSAIC",
|
|
18 => "RIBBON",
|
|
19 => "GRAINBARS",
|
|
_ => "JULIA"
|
|
};
|
|
}
|
|
|
|
private void ToggleFullScreen()
|
|
{
|
|
_graphics.ToggleFullScreen();
|
|
}
|
|
|
|
private void SetRenderQuality(RenderQuality quality)
|
|
{
|
|
_renderQuality = quality;
|
|
_fractalRenderer?.SetQuality(quality);
|
|
}
|
|
|
|
private void CycleWindowResolution()
|
|
{
|
|
if (_graphics.IsFullScreen)
|
|
{
|
|
return;
|
|
}
|
|
|
|
_resolutionIndex = (_resolutionIndex + 1) % _windowedResolutions.Length;
|
|
(int width, int height) = _windowedResolutions[_resolutionIndex];
|
|
_graphics.PreferredBackBufferWidth = width;
|
|
_graphics.PreferredBackBufferHeight = height;
|
|
_graphics.ApplyChanges();
|
|
}
|
|
}
|