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