application.go 14 KB

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