application.go 13 KB

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