public RenderWindow(CurrentRendererPointer renderer, MidiFile midi, RenderSettings settings) : base(16, 9, new GraphicsMode(new ColorFormat(8, 8, 8, 8)), "Render", GameWindowFlags.Default, DisplayDevice.Default) { Width = (int)(DisplayDevice.Default.Width / 1.5); Height = (int)((double)Width / settings.width * settings.height); Location = new Point((DisplayDevice.Default.Width - Width) / 2, (DisplayDevice.Default.Height - Height) / 2); //textEngine = new GLTextEngine(); render = renderer; this.settings = settings; lastTempo = midi.zerothTempo; lock (render) { render.renderer.Tempo = 60000000.0 / midi.zerothTempo; midiTime = -render.renderer.NoteScreenTime; if (settings.timeBasedNotes) { tempoFrameStep = 1000.0 / settings.fps; } else { tempoFrameStep = ((double)midi.division / lastTempo) * (1000000 / settings.fps); } midiTime -= tempoFrameStep * settings.renderSecondsDelay * settings.fps; } globalDisplayNotes = midi.globalDisplayNotes; globalTempoEvents = midi.globalTempoEvents; globalColorEvents = midi.globalColorEvents; globalPlaybackEvents = midi.globalPlaybackEvents; this.midi = midi; if (settings.ffRender) { pixels = new byte[settings.width * settings.height * 4 / settings.downscale / settings.downscale]; ffmpegvideo = startNewFF(settings.ffPath); if (settings.ffRenderMask) { pixelsmask = new byte[settings.width * settings.height * 4 / settings.downscale / settings.downscale]; ffmpegmask = startNewFF(settings.ffMaskPath); } } finalCompositeBuff = new GLPostbuffer(settings.width, settings.height); ffmpegOutputBuff = new GLPostbuffer(settings.width / settings.downscale, settings.height / settings.downscale); downscaleBuff = new GLPostbuffer(settings.width / settings.downscale, settings.height / settings.downscale); GL.GenBuffers(1, out screenQuadBuffer); GL.GenBuffers(1, out screenQuadIndexBuffer); GL.BindBuffer(BufferTarget.ArrayBuffer, screenQuadBuffer); GL.BufferData( BufferTarget.ArrayBuffer, (IntPtr)(screenQuadArray.Length * 8), screenQuadArray, BufferUsageHint.StaticDraw); GL.BindBuffer(BufferTarget.ElementArrayBuffer, screenQuadIndexBuffer); GL.BufferData( BufferTarget.ElementArrayBuffer, (IntPtr)(screenQuadArrayIndex.Length * 4), screenQuadArrayIndex, BufferUsageHint.StaticDraw); postShader = MakeShader(postShaderVert, postShaderFrag); postShaderFlip = MakeShader(postShaderFlipVert, postShaderFrag); postShaderMask = MakeShader(postShaderVert, postShaderFragAlphaMask); postShaderMaskColor = MakeShader(postShaderVert, postShaderFragAlphaMaskColor); postShaderDownscale = MakeShader(postShaderVert, postShaderFragDownscale); uDownscaleRes = GL.GetUniformLocation(postShaderDownscale, "res"); uDownscaleFac = GL.GetUniformLocation(postShaderDownscale, "factor"); }
protected override void OnRenderFrame(FrameEventArgs e) { Task.Factory.StartNew(() => PlaybackLoop(), TaskCreationOptions.LongRunning); SpinWait.SpinUntil(() => playbackLoopStarted); Stopwatch watch = new Stopwatch(); watch.Start(); if (!settings.timeBasedNotes) { tempoFrameStep = ((double)midi.division / lastTempo) * (1000000.0 / settings.fps); } lock (render) { lastDeltaTimeOnScreen = render.renderer.NoteScreenTime; render.renderer.CurrentMidi = midi.info; } int noNoteFrames = 0; long lastNC = 0; bool firstRenderer = true; frameStartTime = DateTime.Now.Ticks; if (settings.timeBasedNotes) { microsecondsPerTick = 10000; } else { microsecondsPerTick = (long)((double)lastTempo / midi.division * 10); } while (settings.running && (noNoteFrames < settings.fps * 5 || midi.unendedTracks != 0)) { if (!settings.Paused || settings.forceReRender) { if (settings.lastyBGChangeTime != lastBGChangeTime) { if (settings.BGImage == null) { if (bgTexID != -1) { GL.DeleteTexture(bgTexID); } bgTexID = -1; } else { if (bgTexID == -1) { bgTexID = GL.GenTexture(); } loadImage(settings.BGImage, bgTexID, false, true); } } lock (render) { try { if (render.disposeQueue.Count != 0) { try { while (true) { var r = render.disposeQueue.Dequeue(); if (r.Initialized) { try { r.Dispose(); } catch { } GC.Collect(); } } } catch (InvalidOperationException) { } } if (!render.renderer.Initialized) { render.renderer.Init(); render.renderer.NoteColors = midi.tracks.Select(t => t.trkColors).ToArray(); render.renderer.ReloadTrackColors(); if (firstRenderer) { firstRenderer = false; midi.SetZeroColors(); } render.renderer.CurrentMidi = midi.info; lock (globalDisplayNotes) { foreach (Note n in globalDisplayNotes) { n.meta = null; } } } render.renderer.Tempo = 60000000.0 / lastTempo; lastDeltaTimeOnScreen = render.renderer.NoteScreenTime; if (settings.timeBasedNotes) { SpinWait.SpinUntil(() => (midi.currentFlexSyncTime > midiTime + lastDeltaTimeOnScreen + tempoFrameStep * settings.tempoMultiplier || midi.unendedTracks == 0) || !settings.running); } else { SpinWait.SpinUntil(() => (midi.currentSyncTime > midiTime + lastDeltaTimeOnScreen + tempoFrameStep * settings.tempoMultiplier || midi.unendedTracks == 0) || !settings.running); } if (!settings.running) { break; } render.renderer.RenderFrame(globalDisplayNotes, midiTime, finalCompositeBuff.BufferID); lastNC = render.renderer.LastNoteCount; if (lastNC == 0 && midi.unendedTracks == 0) { noNoteFrames++; } else { noNoteFrames = 0; } } catch (Exception ex) { MessageBox.Show("The renderer has crashed\n" + ex.Message); break; } } } double mv = 1; if (settings.realtimePlayback) { mv = (DateTime.Now.Ticks - frameStartTime) / microsecondsPerTick / tempoFrameStep; if (mv > settings.fps / 4) { mv = settings.fps / 4; } } lastMV = mv; lock (globalTempoEvents) { while (globalTempoEvents.First != null && midiTime + (tempoFrameStep * mv * settings.tempoMultiplier) > globalTempoEvents.First.pos) { var t = globalTempoEvents.Pop(); if (t.tempo == 0) { Console.WriteLine("Zero tempo event encountered, ignoring"); continue; } var _t = ((t.pos) - midiTime) / (tempoFrameStep * mv * settings.tempoMultiplier); mv *= 1 - _t; if (!settings.timeBasedNotes) { tempoFrameStep = ((double)midi.division / t.tempo) * (1000000.0 / settings.fps); } lastTempo = t.tempo; midiTime = t.pos; } } if (!settings.Paused) { midiTime += mv * tempoFrameStep * settings.tempoMultiplier; } frameStartTime = DateTime.Now.Ticks; if (settings.timeBasedNotes) { microsecondsPerTick = 10000; } else { microsecondsPerTick = (long)(lastTempo / midi.division * 10); } while (globalColorEvents.First != null && globalColorEvents.First.pos < midiTime) { var c = globalColorEvents.Pop(); var track = c.track; if (!settings.ignoreColorEvents) { if (c.channel == 0x7F) { for (int i = 0; i < 16; i++) { c.track.trkColors[i].left = c.col1; c.track.trkColors[i].right = c.col2; c.track.trkColors[i].isDefault = false; } } else { c.track.trkColors[c.channel].left = c.col1; c.track.trkColors[c.channel].right = c.col2; c.track.trkColors[c.channel].isDefault = false; } } } downscaleBuff.BindBuffer(); GL.Clear(ClearBufferMask.ColorBufferBit); GL.Viewport(0, 0, settings.width / settings.downscale, settings.height / settings.downscale); if (bgTexID != -1) { GL.UseProgram(postShaderFlip); GL.BindTexture(TextureTarget.Texture2D, bgTexID); DrawScreenQuad(); } if (settings.downscale > 1) { GL.UseProgram(postShaderDownscale); GL.Uniform1(uDownscaleFac, (int)settings.downscale); GL.Uniform2(uDownscaleRes, new Vector2(settings.width / settings.downscale, settings.height / settings.downscale)); } else { GL.UseProgram(postShader); } finalCompositeBuff.BindTexture(); DrawScreenQuad(); if (settings.ffRender) { if (!settings.ffRenderMask) { GL.UseProgram(postShader); } else { GL.UseProgram(postShaderMaskColor); } finalCompositeBuff.BindTexture(); ffmpegOutputBuff.BindBuffer(); GL.Clear(ClearBufferMask.ColorBufferBit); GL.Viewport(0, 0, settings.width / settings.downscale, settings.height / settings.downscale); downscaleBuff.BindTexture(); DrawScreenQuad(); IntPtr unmanagedPointer = Marshal.AllocHGlobal(pixels.Length); GL.ReadPixels(0, 0, settings.width / settings.downscale, settings.height / settings.downscale, PixelFormat.Bgra, PixelType.UnsignedByte, unmanagedPointer); Marshal.Copy(unmanagedPointer, pixels, 0, pixels.Length); if (lastRenderPush != null) { lastRenderPush.GetAwaiter().GetResult(); } lastRenderPush = Task.Run(() => { ffmpegvideo.StandardInput.BaseStream.Write(pixels, 0, pixels.Length); }); Marshal.FreeHGlobal(unmanagedPointer); if (settings.ffRenderMask) { if (lastRenderPushMask != null) { lastRenderPushMask.GetAwaiter().GetResult(); } GL.UseProgram(postShaderMask); ffmpegOutputBuff.BindBuffer(); GL.Clear(ClearBufferMask.ColorBufferBit); GL.Viewport(0, 0, settings.width / settings.downscale, settings.height / settings.downscale); downscaleBuff.BindTexture(); DrawScreenQuad(); unmanagedPointer = Marshal.AllocHGlobal(pixelsmask.Length); GL.ReadPixels(0, 0, settings.width / settings.downscale, settings.height / settings.downscale, PixelFormat.Bgra, PixelType.UnsignedByte, unmanagedPointer); Marshal.Copy(unmanagedPointer, pixelsmask, 0, pixelsmask.Length); if (lastRenderPush != null) { lastRenderPush.GetAwaiter().GetResult(); } lastRenderPush = Task.Run(() => { ffmpegmask.StandardInput.BaseStream.Write(pixelsmask, 0, pixelsmask.Length); }); Marshal.FreeHGlobal(unmanagedPointer); } } GL.UseProgram(postShader); GLPostbuffer.UnbindBuffers(); GL.Clear(ClearBufferMask.ColorBufferBit); GL.Viewport(0, 0, Width, Height); downscaleBuff.BindTexture(); DrawScreenQuad(); GLPostbuffer.UnbindTextures(); if (settings.ffRender) { VSync = VSyncMode.Off; } else if (settings.vsync) { VSync = VSyncMode.On; } else { VSync = VSyncMode.Off; } try { SwapBuffers(); } catch { break; } ProcessEvents(); double fr = 10000000.0 / watch.ElapsedTicks; settings.liveFps = (settings.liveFps * 2 + fr) / 3; watch.Reset(); watch.Start(); } Console.WriteLine("Left render loop"); settings.running = false; if (settings.ffRender) { if (lastRenderPush != null) { lastRenderPush.GetAwaiter().GetResult(); } ffmpegvideo.StandardInput.Close(); ffmpegvideo.Close(); if (settings.ffRenderMask) { if (lastRenderPushMask != null) { lastRenderPushMask.GetAwaiter().GetResult(); } ffmpegmask.StandardInput.Close(); ffmpegmask.Close(); } } Console.WriteLine("Disposing current renderer"); try { render.renderer.Dispose(); } catch { } try { Console.WriteLine("Disposing of other renderers"); while (render.disposeQueue.Count != 0) { var r = render.disposeQueue.Dequeue(); try { if (r.Initialized) { r.Dispose(); } } catch { } } } catch (InvalidOperationException) { } Console.WriteLine("Disposed of renderers"); globalDisplayNotes = null; globalTempoEvents = null; globalColorEvents = null; pixels = null; pixelsmask = null; if (settings.ffRender) { ffmpegvideo.Dispose(); if (settings.ffRenderMask) { ffmpegmask.Dispose(); } } ffmpegvideo = null; ffmpegmask = null; finalCompositeBuff.Dispose(); ffmpegOutputBuff.Dispose(); downscaleBuff.Dispose(); finalCompositeBuff = null; ffmpegOutputBuff = null; downscaleBuff = null; GL.DeleteBuffers(2, new int[] { screenQuadBuffer, screenQuadIndexBuffer }); GL.DeleteProgram(postShader); GL.DeleteProgram(postShaderMask); GL.DeleteProgram(postShaderMaskColor); GL.DeleteProgram(postShaderDownscale); midi = null; render = null; Console.WriteLine("Closing window"); this.Close(); }
public RenderWindow(CurrentRendererPointer renderer, MidiFile midi, RenderSettings settings) : base(16, 9, new GraphicsMode(new ColorFormat(8, 8, 8, 8)), "Render", GameWindowFlags.Default, DisplayDevice.Default) { Width = (int)(DisplayDevice.Default.Width / 1.5); Height = (int)((double)Width / settings.width * settings.height); Location = new Point((DisplayDevice.Default.Width - Width) / 2, (DisplayDevice.Default.Height - Height) / 2); textEngine = new GLTextEngine(); render = renderer; this.settings = settings; lastTempo = midi.zerothTempo; lock (render) { render.renderer.LastMidiTimePerTick = (double)midi.zerothTempo / midi.division; midiTime = -render.renderer.NoteScreenTime; tempoFrameStep = ((double)midi.division / lastTempo) * (1000000 / settings.fps); midiTime -= tempoFrameStep * settings.renderSecondsDelay * settings.fps; } pixels = new byte[settings.width * settings.height * 3]; //WindowBorder = WindowBorder.Hidden; globalDisplayNotes = midi.globalDisplayNotes; globalTempoEvents = midi.globalTempoEvents; globalColorEvents = midi.globalColorEvents; this.midi = midi; if (settings.ffRender) { string args = "-hide_banner"; if (settings.includeAudio) { double fstep = ((double)midi.division / lastTempo) * (1000000 / settings.fps); double offset = -midiTime / fstep / settings.fps; args = "" + " -f rawvideo -s " + settings.width + "x" + settings.height + " -pix_fmt rgb24 -r " + settings.fps + " -i -" + " -itsoffset " + offset + " -i \"" + settings.audioPath + "\"" + " -vf vflip -vcodec libx264 -pix_fmt yuv420p -acodec aac"; } else { args = "" + " -f rawvideo -s " + settings.width + "x" + settings.height + " -strict -2" + " -pix_fmt rgb24 -r " + settings.fps + " -i -" + " -vf vflip -vcodec libx264 -pix_fmt yuv420p"; } if (settings.useBitrate) { args += " -b:v " + settings.bitrate + "k" + " -maxrate " + settings.bitrate + "k" + " -minrate " + settings.bitrate + "k"; } else { args += " -preset " + settings.crfPreset + " -crf " + settings.crf; } args += " -y \"" + settings.ffPath + "\""; ffmpeg.StartInfo = new ProcessStartInfo("ffmpeg", args); ffmpeg.StartInfo.RedirectStandardInput = true; ffmpeg.StartInfo.UseShellExecute = false; ffmpeg.StartInfo.RedirectStandardError = true; try { ffmpeg.Start(); Console.OpenStandardOutput(); Regex messageMatch = new Regex("\\[.*@.*\\]"); ffmpeg.ErrorDataReceived += (s, e) => { if (e.Data == null) { return; } if (e.Data.Contains("frame=")) { Console.Write(e.Data); Console.SetCursorPosition(0, Console.CursorTop); } if (e.Data.Contains("Conversion failed!")) { Console.ForegroundColor = ConsoleColor.Red; Console.WriteLine("An error occured in FFMPEG, closing!"); Console.ResetColor(); settings.running = false; } if (messageMatch.IsMatch(e.Data)) { Console.WriteLine(e.Data); } }; ffmpeg.BeginErrorReadLine(); } catch (Exception ex) { MessageBox.Show("There was an error starting the ffmpeg process\nNo video will be written\n(Is ffmpeg.exe in the same folder as this program?)\n\n\"" + ex.Message + "\""); settings.ffRender = false; } } else if (settings.imgRender) { if (!Directory.Exists(settings.imgPath)) { Directory.CreateDirectory(settings.imgPath); } } else { if (!settings.vsync) { VSync = VSyncMode.Off; } } finalCompositeBuff = new GLPostbuffer(settings); GL.GenBuffers(1, out screenQuadBuffer); GL.GenBuffers(1, out screenQuadIndexBuffer); GL.BindBuffer(BufferTarget.ArrayBuffer, screenQuadBuffer); GL.BufferData( BufferTarget.ArrayBuffer, (IntPtr)(screenQuadArray.Length * 8), screenQuadArray, BufferUsageHint.StaticDraw); GL.BindBuffer(BufferTarget.ElementArrayBuffer, screenQuadIndexBuffer); GL.BufferData( BufferTarget.ElementArrayBuffer, (IntPtr)(screenQuadArrayIndex.Length * 4), screenQuadArrayIndex, BufferUsageHint.StaticDraw); postShader = MakeShader(postShaderVert, postShaderFrag); }