github.com/graybobo/golang.org-package-offline-cache@v0.0.0-20200626051047-6608995c132f/x/exp/shiny/driver/gldriver/x11.go (about)

     1  // Copyright 2015 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // +build linux,!android
     6  
     7  package gldriver
     8  
     9  /*
    10  #cgo LDFLAGS: -lEGL -lGLESv2 -lX11
    11  
    12  #include <stdint.h>
    13  
    14  void startDriver();
    15  void processEvents();
    16  void makeCurrent(uintptr_t ctx);
    17  void swapBuffers(uintptr_t ctx);
    18  uintptr_t doNewWindow(int width, int height);
    19  uintptr_t doShowWindow(uintptr_t id);
    20  */
    21  import "C"
    22  import (
    23  	"runtime"
    24  	"time"
    25  
    26  	"golang.org/x/exp/shiny/driver/internal/x11key"
    27  	"golang.org/x/exp/shiny/screen"
    28  	"golang.org/x/mobile/event/mouse"
    29  	"golang.org/x/mobile/event/paint"
    30  	"golang.org/x/mobile/event/size"
    31  	"golang.org/x/mobile/geom"
    32  	"golang.org/x/mobile/gl"
    33  )
    34  
    35  func init() {
    36  	// It might not be necessary, but it probably doesn't hurt to try to make
    37  	// 'the main thread' be 'the X11 / OpenGL thread'.
    38  	runtime.LockOSThread()
    39  }
    40  
    41  func newWindow(width, height int32) uintptr {
    42  	retc := make(chan uintptr)
    43  	uic <- uiClosure{
    44  		f: func() uintptr {
    45  			return uintptr(C.doNewWindow(C.int(width), C.int(height)))
    46  		},
    47  		retc: retc,
    48  	}
    49  	return <-retc
    50  }
    51  
    52  func showWindow(w *windowImpl) {
    53  	retc := make(chan uintptr)
    54  	uic <- uiClosure{
    55  		f: func() uintptr {
    56  			return uintptr(C.doShowWindow(C.uintptr_t(w.id)))
    57  		},
    58  		retc: retc,
    59  	}
    60  	w.ctx = <-retc
    61  	w.glctxMu.Lock()
    62  	w.glctx, w.worker = glctx, worker
    63  	w.glctxMu.Unlock()
    64  	go drawLoop(w)
    65  }
    66  
    67  func closeWindow(id uintptr) {
    68  	// TODO.
    69  }
    70  
    71  func drawLoop(w *windowImpl) {
    72  	glcontextc <- w.ctx
    73  	go func() {
    74  		for range w.publish {
    75  			publishc <- w
    76  		}
    77  	}()
    78  }
    79  
    80  var (
    81  	glcontextc = make(chan uintptr)
    82  	publishc   = make(chan *windowImpl)
    83  	uic        = make(chan uiClosure)
    84  
    85  	// TODO: don't assume that there is only one window, and hence only
    86  	// one (global) GL context.
    87  	//
    88  	// TODO: should we be able to make a shiny.Texture before having a
    89  	// shiny.Window's GL context? Should something like gl.IsProgram be a
    90  	// method instead of a function, and have each shiny.Window have its own
    91  	// gl.Context?
    92  	glctx  gl.Context
    93  	worker gl.Worker
    94  )
    95  
    96  // uiClosure is a closure to be run on C's UI thread.
    97  type uiClosure struct {
    98  	f    func() uintptr
    99  	retc chan uintptr
   100  }
   101  
   102  func main(f func(screen.Screen)) error {
   103  	C.startDriver()
   104  	glctx, worker = gl.NewContext()
   105  
   106  	closec := make(chan struct{})
   107  	go func() {
   108  		f(theScreen)
   109  		close(closec)
   110  	}()
   111  
   112  	// heartbeat is a channel that, at regular intervals, directs the select
   113  	// below to also consider X11 events, not just Go events (channel
   114  	// communications).
   115  	//
   116  	// TODO: select instead of poll. Note that knowing whether to call
   117  	// C.processEvents needs to select on a file descriptor, and the other
   118  	// cases below select on Go channels.
   119  	heartbeat := time.NewTicker(time.Second / 60)
   120  	workAvailable := worker.WorkAvailable()
   121  
   122  	for {
   123  		select {
   124  		case <-closec:
   125  			return nil
   126  		case ctx := <-glcontextc:
   127  			// TODO: do we need to synchronize with seeing a size event for
   128  			// this window's context before or after calling makeCurrent?
   129  			// Otherwise, are we racing with the gl.Viewport call? I've
   130  			// occasionally seen a stale viewport, if the window manager sets
   131  			// the window width and height to something other than that
   132  			// requested by XCreateWindow, but it's not easily reproducible.
   133  			C.makeCurrent(C.uintptr_t(ctx))
   134  		case w := <-publishc:
   135  			C.swapBuffers(C.uintptr_t(w.ctx))
   136  			w.publishDone <- screen.PublishResult{}
   137  		case req := <-uic:
   138  			req.retc <- req.f()
   139  		case <-heartbeat.C:
   140  			C.processEvents()
   141  		case <-workAvailable:
   142  			worker.DoWork()
   143  		}
   144  	}
   145  }
   146  
   147  //export onExpose
   148  func onExpose(id uintptr) {
   149  	theScreen.mu.Lock()
   150  	w := theScreen.windows[id]
   151  	theScreen.mu.Unlock()
   152  
   153  	if w == nil {
   154  		return
   155  	}
   156  
   157  	w.Send(paint.Event{External: true})
   158  }
   159  
   160  //export onMouse
   161  func onMouse(id uintptr, x, y, state, button, dir int32) {
   162  	theScreen.mu.Lock()
   163  	w := theScreen.windows[id]
   164  	theScreen.mu.Unlock()
   165  
   166  	if w == nil {
   167  		return
   168  	}
   169  
   170  	// TODO: should a mouse.Event have a separate MouseModifiers field, for
   171  	// which buttons are pressed during a mouse move?
   172  	w.Send(mouse.Event{
   173  		X:         float32(x),
   174  		Y:         float32(y),
   175  		Button:    mouse.Button(button),
   176  		Modifiers: x11key.KeyModifiers(uint16(state)),
   177  		Direction: mouse.Direction(dir),
   178  	})
   179  }
   180  
   181  //export onResize
   182  func onResize(id uintptr, width, height int32) {
   183  	theScreen.mu.Lock()
   184  	w := theScreen.windows[id]
   185  	theScreen.mu.Unlock()
   186  
   187  	if w == nil {
   188  		return
   189  	}
   190  
   191  	// TODO: should this really be done on the receiving end of the w.Events()
   192  	// channel, in the same goroutine as other GL calls in the app's 'business
   193  	// logic'?
   194  	go func() {
   195  		w.glctxMu.Lock()
   196  		w.glctx.Viewport(0, 0, int(width), int(height))
   197  		w.glctxMu.Unlock()
   198  	}()
   199  
   200  	sz := size.Event{
   201  		WidthPx:  int(width),
   202  		HeightPx: int(height),
   203  		WidthPt:  geom.Pt(width),
   204  		HeightPt: geom.Pt(height),
   205  		// TODO: don't assume 72 DPI. DisplayWidth and DisplayWidthMM is
   206  		// probably the best place to start looking.
   207  		PixelsPerPt: 1,
   208  	}
   209  
   210  	w.szMu.Lock()
   211  	w.sz = sz
   212  	w.szMu.Unlock()
   213  
   214  	w.Send(sz)
   215  
   216  	// TODO: lifecycle events?
   217  }