// Initiates a rendering loop. This method returns only when the loop is stopped for whatever reason. // // (Before entering the loop, this method performs a one-off GC invokation.) func (_ *NgLoop) Run() { var ( secTick int runGc = Options.Loop.GcEvery.Frame ) if !Loop.Running { Loop.Running = true glfw.SetTime(0) Loop.Tick.Now = glfw.Time() Core.copyAppToPrep() Core.copyPrepToRend() Loop.Tick.Prev = Loop.Tick.Now Loop.Tick.Now = glfw.Time() Loop.Tick.PrevSec, Loop.Tick.Delta = int(Loop.Tick.Now), Loop.Tick.Now-Loop.Tick.Prev Stats.reset() runtime.GC() Diag.LogMisc("Enter loop...") Loop.Running = glfw.WindowParam(glfw.Opened) == 1 for Loop.Running { // STEP 0. Fire off the prep thread (for next frame) and app thread (for next-after-next frame). thrPrep.Lock() go Loop.onThreadPrep() thrApp.Lock() go Loop.onThreadApp() // STEP 1. Fill the GPU command queue with rendering commands (batched together by the previous prep thread) Stats.FrameRenderCpu.begin() Core.onRender() Stats.FrameRenderCpu.end() // STEP 2. While the GL driver processes its command queue and other CPU cores work on // app and prep threads, this CPU core can now perform some other minor duties Stats.fpsCounter++ Stats.fpsAll++ // This branch runs at most and at least 1x per second if secTick = int(Loop.Tick.Now); secTick != Loop.Tick.PrevSec { Stats.FrameOnSec.begin() Stats.FpsLastSec, Loop.Tick.PrevSec = Stats.fpsCounter, secTick Stats.fpsCounter = 0 if Diag.LogGLErrorsInLoopOnSec { Diag.LogIfGlErr("onSec") } Loop.On.EverySec() runGc = Options.Loop.GcEvery.Sec Stats.FrameOnSec.end() Stats.enable() // the first few frames were warm-ups that don't count towards the stats } // Wait for threads -- waits until both app and prep threads are done and copies stage states around Loop.onWaitForThreads() // Call On.WinThread() -- for main-thread user code (mostly input polling) without affecting On.AppThread Loop.onThreadWin() // Must do this here so that current-tick won't change half-way through OnAppTread(), // and then we'd also like this frame's On.WinThread() to have the same current-tick. Loop.Tick.Prev, Loop.Tick.Now = Loop.Tick.Now, glfw.Time() Stats.Frame.measureStartTime, Loop.Tick.Delta = Loop.Tick.Prev, Loop.Tick.Now-Loop.Tick.Prev Stats.Frame.end() // GC stops-the-world so do it after go-routines have finished. Now is a good time, as the GPU // is likely still busy processing commands from step 1 and won't be interrupted by Go's GC -- // the subsequent buffer-swap step block-waits for the GPU anyway. if runGc { runGc = Options.Loop.GcEvery.Frame Loop.onGC() } // STEP 3. Swap buffers -- this waits for the GPU/GL to finish processing its command // queue filled in Step 1, swap buffers and for V-sync (if any) Loop.onSwap() // Check for resize before next render if (UserIO.Window.lastResize > 0) && ((Loop.Tick.Now - UserIO.Window.lastResize) > UserIO.Window.ResizeMinDelay) { UserIO.Window.lastResize = 0 Core.onResizeWindow(UserIO.Window.width, UserIO.Window.height) } Stats.FrameRenderBoth.combine(&Stats.FrameRenderCpu, &Stats.FrameRenderGpu) if Loop.Delay > 0 { time.Sleep(Loop.Delay) } if Loop.MaxIterations > 0 && Stats.fpsAll >= Loop.MaxIterations { Loop.Running = false } } Loop.Running = false Diag.LogMisc("Exited loop.") Diag.LogIfGlErr("ngine.PostLoop") if false { for rbi, rbe := range Core.Render.Canvases[1].Views[0].Technique_Scene().thrRend.batch.all { println(strf("%d\t==>\tP:%v\tT:%v\tB:%v\tD:%v", rbi, rbe.prog, rbe.texes, Core.Libs.Meshes[rbe.mesh].meshBuffer.glIbo.GlHandle, rbe.dist)) } } } }
func (me *context) SetTime(t float64) { glfw.SetTime(t) }