application.go 16 KB

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