private static void Main(string[] args) { Log.AddWriter(new ConsoleWriter()); Log.AddWriter(FileWriter.CreateDefault()); var tmp = new ProjectSettings(saveOnQuit: false); Options options = ParseCommandLine(args); if (options == null) { return; } _vsync = !options.NoVsync; Log.Debug($"using vsync: {_vsync}, windowed: {options.Windowed}, size: {options.Size}, loop: {options.Loop}, logging: {options.Logging}"); var form = new RenderForm("still::partial") { ClientSize = options.Size, AllowUserResizing = false, Icon = new Icon(@"Resources\t3-editor\images\t3.ico") }; // SwapChain description var desc = new SwapChainDescription() { BufferCount = 3, ModeDescription = new ModeDescription(form.ClientSize.Width, form.ClientSize.Height, new Rational(60, 1), Format.R8G8B8A8_UNorm), IsWindowed = options.Windowed, OutputHandle = form.Handle, SampleDescription = new SampleDescription(1, 0), SwapEffect = SwapEffect.FlipDiscard, Flags = SwapChainFlags.AllowModeSwitch, Usage = Usage.RenderTargetOutput }; // Create Device and SwapChain #if DEBUG || FORCE_D3D_DEBUG var deviceCreationFlags = DeviceCreationFlags.Debug; #else var deviceCreationFlags = DeviceCreationFlags.None; #endif Device.CreateWithSwapChain(DriverType.Hardware, deviceCreationFlags, desc, out var device, out _swapChain); var context = device.ImmediateContext; Device = device; if (_swapChain.IsFullScreen) { Cursor.Hide(); } // Ignore all windows events var factory = _swapChain.GetParent <Factory>(); factory.MakeWindowAssociation(form.Handle, WindowAssociationFlags.IgnoreAll); bool startedWindowed = options.Windowed; form.KeyDown += HandleKeyDown; form.KeyUp += HandleKeyUp; form.KeyUp += (sender, keyArgs) => { if (startedWindowed && keyArgs.Alt && keyArgs.KeyCode == Keys.Enter) { _swapChain.IsFullScreen = !_swapChain.IsFullScreen; RebuildBackBuffer(form, device, ref _renderView, ref _backBuffer, ref _swapChain); if (_swapChain.IsFullScreen) { Cursor.Hide(); } else { Cursor.Show(); } } if (keyArgs.KeyCode == Keys.Left) { Playback.Current.TimeInBars -= 4; } if (keyArgs.KeyCode == Keys.Right) { Playback.Current.TimeInBars += 4; } if (keyArgs.KeyCode == Keys.Escape) { Application.Exit(); } }; // New RenderTargetView from the backbuffer _backBuffer = Texture2D.FromSwapChain <Texture2D>(_swapChain, 0); _renderView = new RenderTargetView(device, _backBuffer); ResourceManager.Init(device); ResourceManager resourceManager = ResourceManager.Instance(); FullScreenVertexShaderId = resourceManager.CreateVertexShaderFromFile(@"Resources\lib\dx11\fullscreen-texture.hlsl", "vsMain", "vs-fullscreen-texture", () => { }); FullScreenPixelShaderId = resourceManager.CreatePixelShaderFromFile(@"Resources\lib\dx11\fullscreen-texture.hlsl", "psMain", "ps-fullscreen-texture", () => { }); Assembly operatorsAssembly; try { operatorsAssembly = Assembly.LoadFrom("Operators.dll"); } catch (Exception e) { Log.Debug($"Error loading operator assembly: '{e.Message}'"); return; } _model = new Model(operatorsAssembly, options.Logging); _model.Load(); var symbols = SymbolRegistry.Entries; var demoSymbol = symbols.First(entry => entry.Value.Name == ProjectSettings.Config.MainOperatorName).Value; _playback = new Playback(); // Create instance of project op, all children are create automatically _project = demoSymbol.CreateInstance(Guid.NewGuid()); _evalContext = new EvaluationContext(); _soundtrack = demoSymbol.AudioClips.SingleOrDefault(ac => ac.IsSoundtrack); var soundtrackDefined = _soundtrack != null && File.Exists(_soundtrack.FilePath); // Init wasapi input if required if (!string.IsNullOrEmpty(ProjectSettings.Config.AudioInputDeviceName)) { Bass.Free(); Bass.Init(); WasapiAudioInput.Initialize(); if (soundtrackDefined) { _playback.Bpm = _soundtrack.Bpm; Log.Warning("Simultaneous audio analysis from project soundtrack and WASAPI is not support. Muting soundtrack"); soundtrackDefined = false; _soundtrack = null; } } if (soundtrackDefined) { _playback.Bpm = _soundtrack.Bpm; // Trigger loading clip AudioEngine.UseAudioClip(_soundtrack, 0); AudioEngine.CompleteFrame(_playback); } // else // { // _playback.PlaybackSpeed = 0.5f; // Todo: Clarify, if this is a work around for default BPM mismatch // } var rasterizerDesc = new RasterizerStateDescription() { FillMode = FillMode.Solid, CullMode = CullMode.None, IsScissorEnabled = false, IsDepthClipEnabled = false }; var rasterizerState = new RasterizerState(device, rasterizerDesc); // Sample some frames to preload all shaders and resources if (soundtrackDefined) { for (double timeInSecs = 0; timeInSecs < _soundtrack.LengthInSeconds; timeInSecs += 2.0) { Playback.Current.TimeInSecs = timeInSecs; Log.Info($"Pre-evaluate at: {timeInSecs:0.00}s / {Playback.Current.TimeInBars:0.00} bars"); DirtyFlag.IncrementGlobalTicks(); DirtyFlag.InvalidationRefFrame++; context.Rasterizer.SetViewport(new Viewport(0, 0, form.ClientSize.Width, form.ClientSize.Height, 0.0f, 1.0f)); context.OutputMerger.SetTargets(_renderView); _evalContext.Reset(); _evalContext.RequestedResolution = new Size2(options.Width, options.Height); if (_project.Outputs[0] is Slot <Texture2D> textureOutput) { textureOutput.Invalidate(); textureOutput.GetValue(_evalContext); var tex = textureOutput.GetValue(_evalContext); if (tex == null) { Log.Error("Failed to initialize texture"); } } Thread.Sleep(20); _swapChain.Present(1, PresentFlags.None); } } // Start playback _playback.Update(); _playback.TimeInBars = 0; _playback.PlaybackSpeed = 1.0; var stopwatch = new Stopwatch(); stopwatch.Start(); // Main loop RenderLoop.Run(form, () => { _playback.Update(); Log.Debug($" render at playback time {_playback.TimeInSecs:0.00}s"); if (_soundtrack != null) { AudioEngine.UseAudioClip(_soundtrack, _playback.TimeInSecs); if (_playback.TimeInSecs >= _soundtrack.LengthInSeconds + _soundtrack.StartTime) { if (options.Loop) { _playback.TimeInSecs = 0.0; } else { Application.Exit(); } } } AudioEngine.CompleteFrame(_playback); DirtyFlag.IncrementGlobalTicks(); DirtyFlag.InvalidationRefFrame++; context.Rasterizer.SetViewport(new Viewport(0, 0, form.ClientSize.Width, form.ClientSize.Height, 0.0f, 1.0f)); context.OutputMerger.SetTargets(_renderView); _evalContext.Reset(); _evalContext.RequestedResolution = new Size2(options.Width, options.Height); if (_project.Outputs[0] is Slot <Texture2D> textureOutput) { textureOutput.Invalidate(); Texture2D tex = textureOutput.GetValue(_evalContext); if (tex != null) { context.Rasterizer.State = rasterizerState; if (resourceManager.Resources[FullScreenVertexShaderId] is VertexShaderResource vsr) { context.VertexShader.Set(vsr.VertexShader); } if (resourceManager.Resources[FullScreenPixelShaderId] is PixelShaderResource psr) { context.PixelShader.Set(psr.PixelShader); } var srv = new ShaderResourceView(device, tex); context.PixelShader.SetShaderResource(0, srv); context.InputAssembler.PrimitiveTopology = PrimitiveTopology.TriangleList; context.ClearRenderTargetView(_renderView, new Color(0.45f, 0.55f, 0.6f, 1.0f)); context.Draw(3, 0); context.PixelShader.SetShaderResource(0, null); } } _swapChain.Present(_vsync ? 1 : 0, PresentFlags.None); }); // Release all resources _renderView.Dispose(); _backBuffer.Dispose(); context.ClearState(); context.Flush(); device.Dispose(); context.Dispose(); _swapChain.Dispose(); factory.Dispose(); }