application.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529
  1. package application
  2. import (
  3. "flag"
  4. "fmt"
  5. "os"
  6. "runtime"
  7. "runtime/pprof"
  8. "runtime/trace"
  9. "time"
  10. "github.com/g3n/engine/audio/al"
  11. "github.com/g3n/engine/audio/vorbis"
  12. "github.com/g3n/engine/camera"
  13. "github.com/g3n/engine/camera/control"
  14. "github.com/g3n/engine/core"
  15. "github.com/g3n/engine/gls"
  16. "github.com/g3n/engine/gui"
  17. "github.com/g3n/engine/math32"
  18. "github.com/g3n/engine/renderer"
  19. "github.com/g3n/engine/util/logger"
  20. "github.com/g3n/engine/window"
  21. )
  22. // Application is a standard application object which can be used as a base for G3N applications.
  23. // It creates a Window, OpenGL state, default cameras, default scene and Gui and has a method to run the render loop.
  24. type Application struct {
  25. core.Dispatcher // Embedded event dispatcher
  26. core.TimerManager // Embedded timer manager
  27. wmgr window.IWindowManager // Window manager
  28. win window.IWindow // Application window
  29. gl *gls.GLS // OpenGL state
  30. log *logger.Logger // Default application logger
  31. renderer *renderer.Renderer // Renderer object
  32. camPersp *camera.Perspective // Perspective camera
  33. camOrtho *camera.Orthographic // Orthographic camera
  34. camera camera.ICamera // Current camera
  35. orbit *control.OrbitControl // Camera orbit controller
  36. frameRater *FrameRater // Render loop frame rater
  37. audioDev *al.Device
  38. scene *core.Node // Node container for 3D tests
  39. guiroot *gui.Root // Gui root panel
  40. frameCount uint64 // Frame counter
  41. frameTime time.Time // Time at the start of the frame
  42. frameDelta time.Duration // Time delta from previous frame
  43. startTime time.Time // Time at the start of the render loop
  44. fullScreen *bool // Full screen option
  45. swapInterval *int // Swap interval option
  46. targetFPS *uint // Target FPS option
  47. noglErrors *bool // No OpenGL check errors options
  48. cpuProfile *string // File to write cpu profile to
  49. execTrace *string // File to write execution trace data to
  50. }
  51. // Options defines initial options passed to the application creation function
  52. type Options struct {
  53. Height int // Initial window height (default is screen width)
  54. Width int // Initial window width (default is screen height)
  55. Fullscreen bool // Window full screen flag (default = false)
  56. LogLevel int // Initial log level (default = DEBUG)
  57. EnableFlags bool // Enable command line flags (default = false)
  58. TargetFPS uint // Desired frames per second rate (default = 60)
  59. }
  60. // OnBeforeRender is the event generated by Application just before rendering the scene/gui
  61. const OnBeforeRender = "util.application.OnBeforeRender"
  62. // OnAfterRender is the event generated by Application just after rendering the scene/gui
  63. const OnAfterRender = "util.application.OnAfterRender"
  64. // appInstance contains the pointer to the single Application instance
  65. var appInstance *Application
  66. // Create creates and returns the application object using the specified name for
  67. // the window title and log messages
  68. // This function must be called only once.
  69. func Create(name string, ops Options) (*Application, error) {
  70. if appInstance != nil {
  71. return nil, fmt.Errorf("Application already created")
  72. }
  73. app := new(Application)
  74. appInstance = app
  75. app.Dispatcher.Initialize()
  76. app.TimerManager.Initialize()
  77. // Initialize options defaults
  78. app.fullScreen = new(bool)
  79. app.swapInterval = new(int)
  80. app.targetFPS = new(uint)
  81. app.noglErrors = new(bool)
  82. app.cpuProfile = new(string)
  83. app.execTrace = new(string)
  84. *app.swapInterval = -1
  85. *app.targetFPS = 60
  86. // Options parameter overrides some options
  87. if ops.TargetFPS != 0 {
  88. *app.fullScreen = ops.Fullscreen
  89. *app.targetFPS = ops.TargetFPS
  90. }
  91. // Creates flags if requested (override options defaults)
  92. if ops.EnableFlags {
  93. app.fullScreen = flag.Bool("fullscreen", false, "Starts application with full screen")
  94. app.swapInterval = flag.Int("swapinterval", -1, "Sets the swap buffers interval to this value")
  95. app.targetFPS = flag.Uint("targetfps", 60, "Sets the frame rate in frames per second")
  96. app.noglErrors = flag.Bool("noglerrors", false, "Do not check OpenGL errors at each call (may increase FPS)")
  97. app.cpuProfile = flag.String("cpuprofile", "", "Activate cpu profiling writing profile to the specified file")
  98. app.execTrace = flag.String("exectrace", "", "Activate execution tracer writing data to the specified file")
  99. }
  100. flag.Parse()
  101. // Creates application logger
  102. app.log = logger.New(name, nil)
  103. app.log.AddWriter(logger.NewConsole(false))
  104. app.log.SetFormat(logger.FTIME | logger.FMICROS)
  105. app.log.SetLevel(ops.LogLevel)
  106. // Window event handling must run on the main OS thread
  107. runtime.LockOSThread()
  108. // Get the window manager
  109. wmgr, err := window.Manager("glfw")
  110. if err != nil {
  111. return nil, err
  112. }
  113. app.wmgr = wmgr
  114. // Get the screen resolution
  115. swidth, sheight := app.wmgr.ScreenResolution(nil)
  116. var posx, posy int
  117. // If not full screen, sets the window size
  118. if !*app.fullScreen {
  119. if ops.Width != 0 {
  120. posx = (swidth - ops.Width) / 2
  121. if posx < 0 {
  122. posx = 0
  123. }
  124. swidth = ops.Width
  125. }
  126. if ops.Height != 0 {
  127. posy = (sheight - ops.Height) / 2
  128. if posy < 0 {
  129. posy = 0
  130. }
  131. sheight = ops.Height
  132. }
  133. }
  134. // Creates window
  135. win, err := app.wmgr.CreateWindow(swidth, sheight, name, *app.fullScreen)
  136. if err != nil {
  137. return nil, err
  138. }
  139. win.SetPos(posx, posy)
  140. app.win = win
  141. // Create OpenGL state
  142. gl, err := gls.New()
  143. if err != nil {
  144. return nil, err
  145. }
  146. app.gl = gl
  147. // Checks OpenGL errors
  148. app.gl.SetCheckErrors(!*app.noglErrors)
  149. // Clears the screen
  150. cc := math32.NewColor("gray")
  151. app.gl.ClearColor(cc.R, cc.G, cc.B, 1)
  152. app.gl.Clear(gls.DEPTH_BUFFER_BIT | gls.STENCIL_BUFFER_BIT | gls.COLOR_BUFFER_BIT)
  153. // Creates perspective camera
  154. width, height := app.win.Size()
  155. aspect := float32(width) / float32(height)
  156. app.camPersp = camera.NewPerspective(65, aspect, 0.01, 1000)
  157. // Creates orthographic camera
  158. app.camOrtho = camera.NewOrthographic(-2, 2, 2, -2, 0.01, 100)
  159. app.camOrtho.SetPosition(0, 0, 3)
  160. app.camOrtho.LookAt(&math32.Vector3{0, 0, 0})
  161. app.camOrtho.SetZoom(1.0)
  162. // Default camera is perspective
  163. app.camera = app.camPersp
  164. // Creates orbit camera control
  165. // It is important to do this after the root panel subscription
  166. // to avoid GUI events being propagated to the orbit control.
  167. app.orbit = control.NewOrbitControl(app.camera, app.win)
  168. // Creates scene for 3D objects
  169. app.scene = core.NewNode()
  170. // Creates gui root panel
  171. app.guiroot = gui.NewRoot(app.gl, app.win)
  172. app.guiroot.SetColor(math32.NewColor("silver"))
  173. // Creates renderer
  174. app.renderer = renderer.NewRenderer(gl)
  175. err = app.renderer.AddDefaultShaders()
  176. if err != nil {
  177. return nil, fmt.Errorf("Error from AddDefaulShaders:%v", err)
  178. }
  179. app.renderer.SetScene(app.scene)
  180. app.renderer.SetGui(app.guiroot)
  181. // Create frame rater
  182. app.frameRater = NewFrameRater(*app.targetFPS)
  183. // Sets the default window resize event handler
  184. app.win.SubscribeID(window.OnWindowSize, app, func(evname string, ev interface{}) {
  185. app.OnWindowResize()
  186. })
  187. app.OnWindowResize()
  188. return app, nil
  189. }
  190. // Get returns the application single instance or nil
  191. // if the application was not created yet
  192. func Get() *Application {
  193. return appInstance
  194. }
  195. // Log returns the application logger
  196. func (app *Application) Log() *logger.Logger {
  197. return app.log
  198. }
  199. // Window returns the application window
  200. func (app *Application) Window() window.IWindow {
  201. return app.win
  202. }
  203. // Gl returns the OpenGL state
  204. func (app *Application) Gl() *gls.GLS {
  205. return app.gl
  206. }
  207. // Gui returns the current application Gui root panel
  208. func (app *Application) Gui() *gui.Root {
  209. return app.guiroot
  210. }
  211. // Scene returns the current application 3D scene
  212. func (app *Application) Scene() *core.Node {
  213. return app.scene
  214. }
  215. // SetScene sets the 3D scene to be rendered
  216. func (app *Application) SetScene(scene *core.Node) {
  217. app.renderer.SetScene(scene)
  218. }
  219. // SetGui sets the root panel of the gui to be rendered
  220. func (app *Application) SetGui(root *gui.Root) {
  221. app.guiroot = root
  222. app.renderer.SetGui(app.guiroot)
  223. }
  224. // SetPanel3D sets the gui panel inside which the 3D scene is shown.
  225. func (app *Application) SetPanel3D(panel3D gui.IPanel) {
  226. app.renderer.SetGuiPanel3D(panel3D)
  227. }
  228. // Panel3D returns the current gui panel where the 3D scene is shown.
  229. func (app *Application) Panel3D() gui.IPanel {
  230. return app.renderer.Panel3D()
  231. }
  232. // CameraPersp returns the application perspective camera
  233. func (app *Application) CameraPersp() *camera.Perspective {
  234. return app.camPersp
  235. }
  236. // CameraOrtho returns the application orthographic camera
  237. func (app *Application) CameraOrtho() *camera.Orthographic {
  238. return app.camOrtho
  239. }
  240. // Camera returns the current application camera
  241. func (app *Application) Camera() camera.ICamera {
  242. return app.camera
  243. }
  244. // SetCamera sets the current application camera
  245. func (app *Application) SetCamera(cam camera.ICamera) {
  246. app.camera = cam
  247. }
  248. // Orbit returns the current camera orbit control
  249. func (app *Application) Orbit() *control.OrbitControl {
  250. return app.orbit
  251. }
  252. // SetOrbit sets the camera orbit control
  253. func (app *Application) SetOrbit(oc *control.OrbitControl) {
  254. app.orbit = oc
  255. }
  256. // FrameRater returns the FrameRater object
  257. func (app *Application) FrameRater() *FrameRater {
  258. return app.frameRater
  259. }
  260. // FrameCount returns the total number of frames since the call to Run()
  261. func (app *Application) FrameCount() uint64 {
  262. return app.frameCount
  263. }
  264. // FrameDelta returns the duration of the previous frame
  265. func (app *Application) FrameDelta() time.Duration {
  266. return app.frameDelta
  267. }
  268. // FrameDeltaSeconds returns the duration of the previous frame
  269. // in float32 seconds
  270. func (app *Application) FrameDeltaSeconds() float32 {
  271. return float32(app.frameDelta.Seconds())
  272. }
  273. // RunTime returns the duration since the call to Run()
  274. func (app *Application) RunTime() time.Duration {
  275. return time.Now().Sub(app.startTime)
  276. }
  277. // RunSeconds returns the elapsed time in seconds since the call to Run()
  278. func (app *Application) RunSeconds() float32 {
  279. return float32(time.Now().Sub(app.startTime).Seconds())
  280. }
  281. // Renderer returns the application renderer
  282. func (app *Application) Renderer() *renderer.Renderer {
  283. return app.renderer
  284. }
  285. // SetCPUProfile must be called before Run() and sets the file name for cpu profiling.
  286. // If set the cpu profiling starts before running the render loop and continues
  287. // till the end of the application.
  288. func (app *Application) SetCPUProfile(fname string) {
  289. *app.cpuProfile = fname
  290. }
  291. // SetOnWindowResize replaces the default window resize handler with the specified one
  292. func (app *Application) SetOnWindowResize(f func(evname string, ev interface{})) {
  293. app.win.UnsubscribeID(window.OnWindowSize, app)
  294. app.win.SubscribeID(window.OnWindowSize, app, f)
  295. }
  296. // Run runs the application render loop
  297. func (app *Application) Run() error {
  298. // Set swap interval
  299. if *app.swapInterval >= 0 {
  300. app.wmgr.SetSwapInterval(*app.swapInterval)
  301. app.log.Debug("Swap interval set to: %v", *app.swapInterval)
  302. }
  303. // Start profiling if requested
  304. if *app.cpuProfile != "" {
  305. f, err := os.Create(*app.cpuProfile)
  306. if err != nil {
  307. return err
  308. }
  309. defer f.Close()
  310. err = pprof.StartCPUProfile(f)
  311. if err != nil {
  312. return err
  313. }
  314. defer pprof.StopCPUProfile()
  315. app.log.Info("Started writing CPU profile to: %s", *app.cpuProfile)
  316. }
  317. // Start execution trace if requested
  318. if *app.execTrace != "" {
  319. f, err := os.Create(*app.execTrace)
  320. if err != nil {
  321. return err
  322. }
  323. defer f.Close()
  324. err = trace.Start(f)
  325. if err != nil {
  326. return err
  327. }
  328. defer trace.Stop()
  329. app.log.Info("Started writing execution trace to: %s", *app.execTrace)
  330. }
  331. app.startTime = time.Now()
  332. app.frameTime = time.Now()
  333. // Render loop
  334. for !app.win.ShouldClose() {
  335. // Starts measuring this frame
  336. app.frameRater.Start()
  337. // Updates frame start and time delta in context
  338. now := time.Now()
  339. app.frameDelta = now.Sub(app.frameTime)
  340. app.frameTime = now
  341. // Process root panel timers
  342. if app.Gui() != nil {
  343. app.Gui().TimerManager.ProcessTimers()
  344. }
  345. // Process application timers
  346. app.ProcessTimers()
  347. // Dispatch before render event
  348. app.Dispatch(OnBeforeRender, nil)
  349. // Renders the current scene and/or gui
  350. rendered, err := app.renderer.Render(app.camera)
  351. if err != nil {
  352. return err
  353. }
  354. // Poll input events and process them
  355. app.wmgr.PollEvents()
  356. if rendered {
  357. app.win.SwapBuffers()
  358. }
  359. // Dispatch after render event
  360. app.Dispatch(OnAfterRender, nil)
  361. // Controls the frame rate
  362. app.frameRater.Wait()
  363. app.frameCount++
  364. }
  365. // Dispose GL resources
  366. if app.scene != nil {
  367. app.scene.DisposeChildren(true)
  368. }
  369. if app.guiroot != nil {
  370. app.guiroot.DisposeChildren(true)
  371. }
  372. // Terminates window manager
  373. app.wmgr.Terminate()
  374. // This is important when using the execution tracer
  375. runtime.UnlockOSThread()
  376. return nil
  377. }
  378. // OpenDefaultAudioDevice opens the default audio device setting it to the current context
  379. func (app *Application) OpenDefaultAudioDevice() error {
  380. // Opens default audio device
  381. dev, err := al.OpenDevice("")
  382. if err == nil {
  383. return err
  384. }
  385. app.audioDev = dev
  386. // Creates audio context with auxiliary sends
  387. var attribs []int
  388. //if app.audioEFX {
  389. // attribs = []int{al.MAX_AUXILIARY_SENDS, 4}
  390. //}
  391. acx, err := al.CreateContext(dev, attribs)
  392. if err != nil {
  393. return err
  394. }
  395. // Makes the context the current one
  396. err = al.MakeContextCurrent(acx)
  397. if err != nil {
  398. return fmt.Errorf("Error setting audio context current:%s", err)
  399. }
  400. // Logs audio library versions
  401. app.log.Debug("%s version: %s", al.GetString(al.Vendor), al.GetString(al.Version))
  402. app.log.Debug("%s", vorbis.VersionString())
  403. return nil
  404. }
  405. // Quit ends the application
  406. func (app *Application) Quit() {
  407. app.win.SetShouldClose(true)
  408. }
  409. // OnWindowResize is default handler for window resize events.
  410. func (app *Application) OnWindowResize() {
  411. // Get window size and sets the viewport to the same size
  412. width, height := app.win.Size()
  413. app.gl.Viewport(0, 0, int32(width), int32(height))
  414. // Sets perspective camera aspect ratio
  415. aspect := float32(width) / float32(height)
  416. app.camPersp.SetAspect(aspect)
  417. // Sets the GUI root panel size to the size of the screen
  418. if app.guiroot != nil {
  419. app.guiroot.SetSize(float32(width), float32(height))
  420. }
  421. }