1
0
Fork 0

Adding upstream version 0.0~git20250520.a1d9079+dfsg.

Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
Daniel Baumann 2025-05-24 19:46:29 +02:00
parent 590ac7ff5f
commit 20149b7f3a
Signed by: daniel
GPG key ID: FBB4F0E80A80222F
456 changed files with 70406 additions and 0 deletions

2
exp/README Normal file
View file

@ -0,0 +1,2 @@
golang.org/x/mobile/exp contains experimental packages for mobile app
development.

220
exp/app/debug/fps.go Normal file
View file

@ -0,0 +1,220 @@
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build darwin || linux || windows
// Package debug provides GL-based debugging tools for apps.
package debug
import (
"image"
"image/color"
"image/draw"
"time"
"golang.org/x/mobile/event/size"
"golang.org/x/mobile/exp/gl/glutil"
"golang.org/x/mobile/geom"
)
// FPS draws a count of the frames rendered per second.
type FPS struct {
sz size.Event
images *glutil.Images
m *glutil.Image
lastDraw time.Time
// TODO: store *gl.Context
}
// NewFPS creates an FPS tied to the current GL context.
func NewFPS(images *glutil.Images) *FPS {
return &FPS{
lastDraw: time.Now(),
images: images,
}
}
// Draw draws the per second framerate in the bottom-left of the screen.
func (p *FPS) Draw(sz size.Event) {
const imgW, imgH = 7*(fontWidth+1) + 1, fontHeight + 2
if sz.WidthPx == 0 && sz.HeightPx == 0 {
return
}
if p.sz != sz {
p.sz = sz
if p.m != nil {
p.m.Release()
}
p.m = p.images.NewImage(imgW, imgH)
}
display := [7]byte{
4: 'F',
5: 'P',
6: 'S',
}
now := time.Now()
f := 0
if dur := now.Sub(p.lastDraw); dur > 0 {
f = int(time.Second / dur)
}
display[2] = '0' + byte((f/1e0)%10)
display[1] = '0' + byte((f/1e1)%10)
display[0] = '0' + byte((f/1e2)%10)
draw.Draw(p.m.RGBA, p.m.RGBA.Bounds(), image.White, image.Point{}, draw.Src)
for i, c := range display {
glyph := glyphs[c]
if len(glyph) != fontWidth*fontHeight {
continue
}
for y := 0; y < fontHeight; y++ {
for x := 0; x < fontWidth; x++ {
if glyph[fontWidth*y+x] == ' ' {
continue
}
p.m.RGBA.SetRGBA((fontWidth+1)*i+x+1, y+1, color.RGBA{A: 0xff})
}
}
}
p.m.Upload()
p.m.Draw(
sz,
geom.Point{0, sz.HeightPt - imgH},
geom.Point{imgW, sz.HeightPt - imgH},
geom.Point{0, sz.HeightPt},
p.m.RGBA.Bounds(),
)
p.lastDraw = now
}
func (f *FPS) Release() {
if f.m != nil {
f.m.Release()
f.m = nil
f.images = nil
}
}
const (
fontWidth = 5
fontHeight = 7
)
// glyphs comes from the 6x10 fixed font from the plan9port:
// https://github.com/9fans/plan9port/tree/master/font/fixed
//
// 6x10 becomes 5x7 because each glyph has a 1-pixel margin plus space for
// descenders.
//
// Its README file says that those fonts were converted from XFree86, and are
// in the public domain.
var glyphs = [256]string{
'0': "" +
" X " +
" X X " +
"X X" +
"X X" +
"X X" +
" X X " +
" X ",
'1': "" +
" X " +
" XX " +
"X X " +
" X " +
" X " +
" X " +
"XXXXX",
'2': "" +
" XXX " +
"X X" +
" X" +
" XX " +
" X " +
"X " +
"XXXXX",
'3': "" +
"XXXXX" +
" X" +
" X " +
" XX " +
" X" +
"X X" +
" XXX ",
'4': "" +
" X " +
" XX " +
" X X " +
"X X " +
"XXXXX" +
" X " +
" X ",
'5': "" +
"XXXXX" +
"X " +
"X XX " +
"XX X" +
" X" +
"X X" +
" XXX ",
'6': "" +
" XX " +
" X " +
"X " +
"X XX " +
"XX X" +
"X X" +
" XXX ",
'7': "" +
"XXXXX" +
" X" +
" X " +
" X " +
" X " +
" X " +
" X ",
'8': "" +
" XXX " +
"X X" +
"X X" +
" XXX " +
"X X" +
"X X" +
" XXX ",
'9': "" +
" XXX " +
"X X" +
"X XX" +
" XX X" +
" X" +
" X " +
" XX ",
'F': "" +
"XXXXX" +
"X " +
"X " +
"XXXX " +
"X " +
"X " +
"X ",
'P': "" +
"XXXX " +
"X X" +
"X X" +
"XXXX " +
"X " +
"X " +
"X ",
'S': "" +
" XXX " +
"X X" +
"X " +
" XXX " +
" X" +
"X X" +
" XXX ",
}

456
exp/audio/al/al.go Normal file
View file

@ -0,0 +1,456 @@
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build darwin || linux || windows
// Package al provides OpenAL Soft bindings for Go.
//
// Calls are not safe for concurrent use.
//
// More information about OpenAL Soft is available at
// http://www.openal.org/documentation/openal-1.1-specification.pdf.
//
// In order to use this package on Linux desktop distros,
// you will need OpenAL library as an external dependency.
// On Ubuntu 14.04 'Trusty', you may have to install this library
// by running the command below.
//
// sudo apt-get install libopenal-dev
//
// When compiled for Android, this package uses OpenAL Soft. Please add its
// license file to the open source notices of your application.
// OpenAL Soft's license file could be found at
// http://repo.or.cz/w/openal-soft.git/blob/HEAD:/COPYING.
package al
// Capability represents OpenAL extension capabilities.
type Capability int32
// Enable enables a capability.
func Enable(c Capability) {
alEnable(int32(c))
}
// Disable disables a capability.
func Disable(c Capability) {
alDisable(int32(c))
}
// Enabled reports whether the specified capability is enabled.
func Enabled(c Capability) bool {
return alIsEnabled(int32(c))
}
// Vector represents an vector in a Cartesian coordinate system.
type Vector [3]float32
// Orientation represents the angular position of an object in a
// right-handed Cartesian coordinate system.
// A cross product between the forward and up vector returns a vector
// that points to the right.
type Orientation struct {
// Forward vector is the direction that the object is looking at.
Forward Vector
// Up vector represents the rotation of the object.
Up Vector
}
func orientationFromSlice(v []float32) Orientation {
return Orientation{
Forward: Vector{v[0], v[1], v[2]},
Up: Vector{v[3], v[4], v[5]},
}
}
func (v Orientation) slice() []float32 {
return []float32{v.Forward[0], v.Forward[1], v.Forward[2], v.Up[0], v.Up[1], v.Up[2]}
}
// Geti returns the int32 value of the given parameter.
func Geti(param int) int32 {
return alGetInteger(param)
}
// Getiv returns the int32 vector value of the given parameter.
func Getiv(param int, v []int32) {
alGetIntegerv(param, v)
}
// Getf returns the float32 value of the given parameter.
func Getf(param int) float32 {
return alGetFloat(param)
}
// Getfv returns the float32 vector value of the given parameter.
func Getfv(param int, v []float32) {
alGetFloatv(param, v[:])
}
// Getb returns the bool value of the given parameter.
func Getb(param int) bool {
return alGetBoolean(param)
}
// Getbv returns the bool vector value of the given parameter.
func Getbv(param int, v []bool) {
alGetBooleanv(param, v)
}
// GetString returns the string value of the given parameter.
func GetString(param int) string {
return alGetString(param)
}
// DistanceModel returns the distance model.
func DistanceModel() int32 {
return Geti(paramDistanceModel)
}
// SetDistanceModel sets the distance model.
func SetDistanceModel(v int32) {
alDistanceModel(v)
}
// DopplerFactor returns the doppler factor.
func DopplerFactor() float32 {
return Getf(paramDopplerFactor)
}
// SetDopplerFactor sets the doppler factor.
func SetDopplerFactor(v float32) {
alDopplerFactor(v)
}
// DopplerVelocity returns the doppler velocity.
func DopplerVelocity() float32 {
return Getf(paramDopplerVelocity)
}
// SetDopplerVelocity sets the doppler velocity.
func SetDopplerVelocity(v float32) {
alDopplerVelocity(v)
}
// SpeedOfSound is the speed of sound in meters per second (m/s).
func SpeedOfSound() float32 {
return Getf(paramSpeedOfSound)
}
// SetSpeedOfSound sets the speed of sound, its unit should be meters per second (m/s).
func SetSpeedOfSound(v float32) {
alSpeedOfSound(v)
}
// Vendor returns the vendor.
func Vendor() string {
return GetString(paramVendor)
}
// Version returns the version string.
func Version() string {
return GetString(paramVersion)
}
// Renderer returns the renderer information.
func Renderer() string {
return GetString(paramRenderer)
}
// Extensions returns the enabled extensions.
func Extensions() string {
return GetString(paramExtensions)
}
// Error returns the most recently generated error.
func Error() int32 {
return alGetError()
}
// Source represents an individual sound source in 3D-space.
// They take PCM data, apply modifications and then submit them to
// be mixed according to their spatial location.
type Source uint32
// GenSources generates n new sources. These sources should be deleted
// once they are not in use.
func GenSources(n int) []Source {
return alGenSources(n)
}
// PlaySources plays the sources.
func PlaySources(source ...Source) {
alSourcePlayv(source)
}
// PauseSources pauses the sources.
func PauseSources(source ...Source) {
alSourcePausev(source)
}
// StopSources stops the sources.
func StopSources(source ...Source) {
alSourceStopv(source)
}
// RewindSources rewinds the sources to their beginning positions.
func RewindSources(source ...Source) {
alSourceRewindv(source)
}
// DeleteSources deletes the sources.
func DeleteSources(source ...Source) {
alDeleteSources(source)
}
// Gain returns the source gain.
func (s Source) Gain() float32 {
return s.Getf(paramGain)
}
// SetGain sets the source gain.
func (s Source) SetGain(v float32) {
s.Setf(paramGain, v)
}
// MinGain returns the source's minimum gain setting.
func (s Source) MinGain() float32 {
return s.Getf(paramMinGain)
}
// SetMinGain sets the source's minimum gain setting.
func (s Source) SetMinGain(v float32) {
s.Setf(paramMinGain, v)
}
// MaxGain returns the source's maximum gain setting.
func (s Source) MaxGain() float32 {
return s.Getf(paramMaxGain)
}
// SetMaxGain sets the source's maximum gain setting.
func (s Source) SetMaxGain(v float32) {
s.Setf(paramMaxGain, v)
}
// Position returns the position of the source.
func (s Source) Position() Vector {
v := Vector{}
s.Getfv(paramPosition, v[:])
return v
}
// SetPosition sets the position of the source.
func (s Source) SetPosition(v Vector) {
s.Setfv(paramPosition, v[:])
}
// Velocity returns the source's velocity.
func (s Source) Velocity() Vector {
v := Vector{}
s.Getfv(paramVelocity, v[:])
return v
}
// SetVelocity sets the source's velocity.
func (s Source) SetVelocity(v Vector) {
s.Setfv(paramVelocity, v[:])
}
// Orientation returns the orientation of the source.
func (s Source) Orientation() Orientation {
v := make([]float32, 6)
s.Getfv(paramOrientation, v)
return orientationFromSlice(v)
}
// SetOrientation sets the orientation of the source.
func (s Source) SetOrientation(o Orientation) {
s.Setfv(paramOrientation, o.slice())
}
// State returns the playing state of the source.
func (s Source) State() int32 {
return s.Geti(paramSourceState)
}
// BuffersQueued returns the number of the queued buffers.
func (s Source) BuffersQueued() int32 {
return s.Geti(paramBuffersQueued)
}
// BuffersProcessed returns the number of the processed buffers.
func (s Source) BuffersProcessed() int32 {
return s.Geti(paramBuffersProcessed)
}
// OffsetSeconds returns the current playback position of the source in seconds.
func (s Source) OffsetSeconds() int32 {
return s.Geti(paramSecOffset)
}
// OffsetSample returns the sample offset of the current playback position.
func (s Source) OffsetSample() int32 {
return s.Geti(paramSampleOffset)
}
// OffsetByte returns the byte offset of the current playback position.
func (s Source) OffsetByte() int32 {
return s.Geti(paramByteOffset)
}
// Geti returns the int32 value of the given parameter.
func (s Source) Geti(param int) int32 {
return alGetSourcei(s, param)
}
// Getf returns the float32 value of the given parameter.
func (s Source) Getf(param int) float32 {
return alGetSourcef(s, param)
}
// Getfv returns the float32 vector value of the given parameter.
func (s Source) Getfv(param int, v []float32) {
alGetSourcefv(s, param, v)
}
// Seti sets an int32 value for the given parameter.
func (s Source) Seti(param int, v int32) {
alSourcei(s, param, v)
}
// Setf sets a float32 value for the given parameter.
func (s Source) Setf(param int, v float32) {
alSourcef(s, param, v)
}
// Setfv sets a float32 vector value for the given parameter.
func (s Source) Setfv(param int, v []float32) {
alSourcefv(s, param, v)
}
// QueueBuffers adds the buffers to the buffer queue.
func (s Source) QueueBuffers(buffer ...Buffer) {
alSourceQueueBuffers(s, buffer)
}
// UnqueueBuffers removes the specified buffers from the buffer queue.
func (s Source) UnqueueBuffers(buffer ...Buffer) {
alSourceUnqueueBuffers(s, buffer)
}
// ListenerGain returns the total gain applied to the final mix.
func ListenerGain() float32 {
return GetListenerf(paramGain)
}
// ListenerPosition returns the position of the listener.
func ListenerPosition() Vector {
v := Vector{}
GetListenerfv(paramPosition, v[:])
return v
}
// ListenerVelocity returns the velocity of the listener.
func ListenerVelocity() Vector {
v := Vector{}
GetListenerfv(paramVelocity, v[:])
return v
}
// ListenerOrientation returns the orientation of the listener.
func ListenerOrientation() Orientation {
v := make([]float32, 6)
GetListenerfv(paramOrientation, v)
return orientationFromSlice(v)
}
// SetListenerGain sets the total gain that will be applied to the final mix.
func SetListenerGain(v float32) {
SetListenerf(paramGain, v)
}
// SetListenerPosition sets the position of the listener.
func SetListenerPosition(v Vector) {
SetListenerfv(paramPosition, v[:])
}
// SetListenerVelocity sets the velocity of the listener.
func SetListenerVelocity(v Vector) {
SetListenerfv(paramVelocity, v[:])
}
// SetListenerOrientation sets the orientation of the listener.
func SetListenerOrientation(v Orientation) {
SetListenerfv(paramOrientation, v.slice())
}
// GetListenerf returns the float32 value of the listener parameter.
func GetListenerf(param int) float32 {
return alGetListenerf(param)
}
// GetListenerfv returns the float32 vector value of the listener parameter.
func GetListenerfv(param int, v []float32) {
alGetListenerfv(param, v)
}
// SetListenerf sets the float32 value for the listener parameter.
func SetListenerf(param int, v float32) {
alListenerf(param, v)
}
// SetListenerfv sets the float32 vector value of the listener parameter.
func SetListenerfv(param int, v []float32) {
alListenerfv(param, v)
}
// A buffer represents a chunk of PCM audio data that could be buffered to an audio
// source. A single buffer could be shared between multiple sources.
type Buffer uint32
// GenBuffers generates n new buffers. The generated buffers should be deleted
// once they are no longer in use.
func GenBuffers(n int) []Buffer {
return alGenBuffers(n)
}
// DeleteBuffers deletes the buffers.
func DeleteBuffers(buffer ...Buffer) {
alDeleteBuffers(buffer)
}
// Geti returns the int32 value of the given parameter.
func (b Buffer) Geti(param int) int32 {
return b.Geti(param)
}
// Frequency returns the frequency of the buffer data in Hertz (Hz).
func (b Buffer) Frequency() int32 {
return b.Geti(paramFreq)
}
// Bits return the number of bits used to represent a sample.
func (b Buffer) Bits() int32 {
return b.Geti(paramBits)
}
// Channels return the number of the audio channels.
func (b Buffer) Channels() int32 {
return b.Geti(paramChannels)
}
// Size returns the size of the data.
func (b Buffer) Size() int32 {
return b.Geti(paramSize)
}
// BufferData buffers PCM data to the current buffer.
func (b Buffer) BufferData(format uint32, data []byte, freq int32) {
alBufferData(b, format, data, freq)
}
// Valid reports whether the buffer exists and is valid.
func (b Buffer) Valid() bool {
return alIsBuffer(b)
}

486
exp/audio/al/al_android.go Normal file
View file

@ -0,0 +1,486 @@
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package al
/*
#include <stdlib.h>
#include <string.h>
#include <dlfcn.h>
#include <jni.h>
#include <limits.h>
#include <AL/al.h>
#include <AL/alc.h>
void al_init(uintptr_t java_vm, uintptr_t jni_env, jobject context, void** handle) {
JavaVM* vm = (JavaVM*)java_vm;
JNIEnv* env = (JNIEnv*)jni_env;
jclass android_content_Context = (*env)->FindClass(env, "android/content/Context");
jmethodID get_application_info = (*env)->GetMethodID(env, android_content_Context, "getApplicationInfo", "()Landroid/content/pm/ApplicationInfo;");
jclass android_content_pm_ApplicationInfo = (*env)->FindClass(env, "android/content/pm/ApplicationInfo");
jfieldID native_library_dir = (*env)->GetFieldID(env, android_content_pm_ApplicationInfo, "nativeLibraryDir", "Ljava/lang/String;");
jobject app_info = (*env)->CallObjectMethod(env, context, get_application_info);
jstring native_dir = (*env)->GetObjectField(env, app_info, native_library_dir);
const char *cnative_dir = (*env)->GetStringUTFChars(env, native_dir, 0);
char lib_path[PATH_MAX] = "";
strlcat(lib_path, cnative_dir, sizeof(lib_path));
strlcat(lib_path, "/libopenal.so", sizeof(lib_path));
*handle = dlopen(lib_path, RTLD_LAZY);
(*env)->ReleaseStringUTFChars(env, native_dir, cnative_dir);
}
void call_alEnable(LPALENABLE fn, ALenum capability) {
fn(capability);
}
void call_alDisable(LPALDISABLE fn, ALenum capability) {
fn(capability);
}
ALboolean call_alIsEnabled(LPALISENABLED fn, ALenum capability) {
return fn(capability);
}
ALint call_alGetInteger(LPALGETINTEGER fn, ALenum p) {
return fn(p);
}
void call_alGetIntegerv(LPALGETINTEGERV fn, ALenum p, ALint* v) {
fn(p, v);
}
ALfloat call_alGetFloat(LPALGETFLOAT fn, ALenum p) {
return fn(p);
}
void call_alGetFloatv(LPALGETFLOATV fn, ALenum p, ALfloat* v) {
fn(p, v);
}
ALboolean call_alGetBoolean(LPALGETBOOLEAN fn, ALenum p) {
return fn(p);
}
void call_alGetBooleanv(LPALGETBOOLEANV fn, ALenum p, ALboolean* v) {
fn(p, v);
}
const char* call_alGetString(LPALGETSTRING fn, ALenum p) {
return fn(p);
}
void call_alDistanceModel(LPALDISTANCEMODEL fn, ALenum v) {
fn(v);
}
void call_alDopplerFactor(LPALDOPPLERFACTOR fn, ALfloat v) {
fn(v);
}
void call_alDopplerVelocity(LPALDOPPLERVELOCITY fn, ALfloat v) {
fn(v);
}
void call_alSpeedOfSound(LPALSPEEDOFSOUND fn, ALfloat v) {
fn(v);
}
ALint call_alGetError(LPALGETERROR fn) {
return fn();
}
void call_alGenSources(LPALGENSOURCES fn, ALsizei n, ALuint* s) {
fn(n, s);
}
void call_alSourcePlayv(LPALSOURCEPLAYV fn, ALsizei n, const ALuint* s) {
fn(n, s);
}
void call_alSourcePausev(LPALSOURCEPAUSEV fn, ALsizei n, const ALuint* s) {
fn(n, s);
}
void call_alSourceStopv(LPALSOURCESTOPV fn, ALsizei n, const ALuint* s) {
fn(n, s);
}
void call_alSourceRewindv(LPALSOURCEREWINDV fn, ALsizei n, const ALuint* s) {
fn(n, s);
}
void call_alDeleteSources(LPALDELETESOURCES fn, ALsizei n, const ALuint* s) {
fn(n, s);
}
void call_alGetSourcei(LPALGETSOURCEI fn, ALuint s, ALenum k, ALint* v) {
fn(s, k, v);
}
void call_alGetSourcef(LPALGETSOURCEF fn, ALuint s, ALenum k, ALfloat* v) {
fn(s, k, v);
}
void call_alGetSourcefv(LPALGETSOURCEFV fn, ALuint s, ALenum k, ALfloat* v) {
fn(s, k, v);
}
void call_alSourcei(LPALSOURCEI fn, ALuint s, ALenum k, ALint v) {
fn(s, k, v);
}
void call_alSourcef(LPALSOURCEF fn, ALuint s, ALenum k, ALfloat v) {
fn(s, k, v);
}
void call_alSourcefv(LPALSOURCEFV fn, ALuint s, ALenum k, const ALfloat* v) {
fn(s, k, v);
}
void call_alSourceQueueBuffers(LPALSOURCEQUEUEBUFFERS fn, ALuint s, ALsizei n, const ALuint* b) {
fn(s, n, b);
}
void call_alSourceUnqueueBuffers(LPALSOURCEUNQUEUEBUFFERS fn, ALuint s, ALsizei n, ALuint* b) {
fn(s, n, b);
}
void call_alGetListenerf(LPALGETLISTENERF fn, ALenum k, ALfloat* v) {
fn(k, v);
}
void call_alGetListenerfv(LPALLISTENERFV fn, ALenum k, ALfloat* v) {
fn(k, v);
}
void call_alListenerf(LPALLISTENERF fn, ALenum k, ALfloat v) {
fn(k, v);
}
void call_alListenerfv(LPALLISTENERFV fn, ALenum k, const ALfloat* v) {
fn(k, v);
}
void call_alGenBuffers(LPALGENBUFFERS fn, ALsizei n, ALuint* v) {
fn(n, v);
}
void call_alDeleteBuffers(LPALDELETEBUFFERS fn, ALsizei n, ALuint* v) {
fn(n, v);
}
void call_alGetBufferi(LPALGETBUFFERI fn, ALuint b, ALenum k, ALint* v) {
fn(b, k, v);
}
void call_alBufferData(LPALBUFFERDATA fn, ALuint b, ALenum format, const ALvoid* data, ALsizei size, ALsizei freq) {
fn(b, format, data, size, freq);
}
ALboolean call_alIsBuffer(LPALISBUFFER fn, ALuint b) {
return fn(b);
}
*/
import "C"
import (
"errors"
"log"
"unsafe"
"golang.org/x/mobile/internal/mobileinit"
)
var (
alHandle unsafe.Pointer
alEnableFunc C.LPALENABLE
alDisableFunc C.LPALDISABLE
alIsEnabledFunc C.LPALISENABLED
alGetIntegerFunc C.LPALGETINTEGER
alGetIntegervFunc C.LPALGETINTEGERV
alGetFloatFunc C.LPALGETFLOAT
alGetFloatvFunc C.LPALGETFLOATV
alGetBooleanFunc C.LPALGETBOOLEAN
alGetBooleanvFunc C.LPALGETBOOLEANV
alGetStringFunc C.LPALGETSTRING
alDistanceModelFunc C.LPALDISTANCEMODEL
alDopplerFactorFunc C.LPALDOPPLERFACTOR
alDopplerVelocityFunc C.LPALDOPPLERVELOCITY
alSpeedOfSoundFunc C.LPALSPEEDOFSOUND
alGetErrorFunc C.LPALGETERROR
alGenSourcesFunc C.LPALGENSOURCES
alSourcePlayvFunc C.LPALSOURCEPLAYV
alSourcePausevFunc C.LPALSOURCEPAUSEV
alSourceStopvFunc C.LPALSOURCESTOPV
alSourceRewindvFunc C.LPALSOURCEREWINDV
alDeleteSourcesFunc C.LPALDELETESOURCES
alGetSourceiFunc C.LPALGETSOURCEI
alGetSourcefFunc C.LPALGETSOURCEF
alGetSourcefvFunc C.LPALGETSOURCEFV
alSourceiFunc C.LPALSOURCEI
alSourcefFunc C.LPALSOURCEF
alSourcefvFunc C.LPALSOURCEFV
alSourceQueueBuffersFunc C.LPALSOURCEQUEUEBUFFERS
alSourceUnqueueBuffersFunc C.LPALSOURCEUNQUEUEBUFFERS
alGetListenerfFunc C.LPALGETLISTENERF
alGetListenerfvFunc C.LPALGETLISTENERFV
alListenerfFunc C.LPALLISTENERF
alListenerfvFunc C.LPALLISTENERFV
alGenBuffersFunc C.LPALGENBUFFERS
alDeleteBuffersFunc C.LPALDELETEBUFFERS
alGetBufferiFunc C.LPALGETBUFFERI
alBufferDataFunc C.LPALBUFFERDATA
alIsBufferFunc C.LPALISBUFFER
alcGetErrorFunc C.LPALCGETERROR
alcOpenDeviceFunc C.LPALCOPENDEVICE
alcCloseDeviceFunc C.LPALCCLOSEDEVICE
alcCreateContextFunc C.LPALCCREATECONTEXT
alcMakeContextCurrentFunc C.LPALCMAKECONTEXTCURRENT
alcDestroyContextFunc C.LPALCDESTROYCONTEXT
)
func initAL() {
err := mobileinit.RunOnJVM(func(vm, env, ctx uintptr) error {
C.al_init(C.uintptr_t(vm), C.uintptr_t(env), C.jobject(ctx), &alHandle)
if alHandle == nil {
return errors.New("al: cannot load libopenal.so")
}
return nil
})
if err != nil {
log.Fatalf("al: %v", err)
}
alEnableFunc = C.LPALENABLE(fn("alEnable"))
alDisableFunc = C.LPALDISABLE(fn("alDisable"))
alIsEnabledFunc = C.LPALISENABLED(fn("alIsEnabled"))
alGetIntegerFunc = C.LPALGETINTEGER(fn("alGetInteger"))
alGetIntegervFunc = C.LPALGETINTEGERV(fn("alGetIntegerv"))
alGetFloatFunc = C.LPALGETFLOAT(fn("alGetFloat"))
alGetFloatvFunc = C.LPALGETFLOATV(fn("alGetFloatv"))
alGetBooleanFunc = C.LPALGETBOOLEAN(fn("alGetBoolean"))
alGetBooleanvFunc = C.LPALGETBOOLEANV(fn("alGetBooleanv"))
alGetStringFunc = C.LPALGETSTRING(fn("alGetString"))
alDistanceModelFunc = C.LPALDISTANCEMODEL(fn("alDistanceModel"))
alDopplerFactorFunc = C.LPALDOPPLERFACTOR(fn("alDopplerFactor"))
alDopplerVelocityFunc = C.LPALDOPPLERVELOCITY(fn("alDopplerVelocity"))
alSpeedOfSoundFunc = C.LPALSPEEDOFSOUND(fn("alSpeedOfSound"))
alGetErrorFunc = C.LPALGETERROR(fn("alGetError"))
alGenSourcesFunc = C.LPALGENSOURCES(fn("alGenSources"))
alSourcePlayvFunc = C.LPALSOURCEPLAYV(fn("alSourcePlayv"))
alSourcePausevFunc = C.LPALSOURCEPAUSEV(fn("alSourcePausev"))
alSourceStopvFunc = C.LPALSOURCESTOPV(fn("alSourceStopv"))
alSourceRewindvFunc = C.LPALSOURCEREWINDV(fn("alSourceRewindv"))
alDeleteSourcesFunc = C.LPALDELETESOURCES(fn("alDeleteSources"))
alGetSourceiFunc = C.LPALGETSOURCEI(fn("alGetSourcei"))
alGetSourcefFunc = C.LPALGETSOURCEF(fn("alGetSourcef"))
alGetSourcefvFunc = C.LPALGETSOURCEFV(fn("alGetSourcefv"))
alSourceiFunc = C.LPALSOURCEI(fn("alSourcei"))
alSourcefFunc = C.LPALSOURCEF(fn("alSourcef"))
alSourcefvFunc = C.LPALSOURCEFV(fn("alSourcefv"))
alSourceQueueBuffersFunc = C.LPALSOURCEQUEUEBUFFERS(fn("alSourceQueueBuffers"))
alSourceUnqueueBuffersFunc = C.LPALSOURCEUNQUEUEBUFFERS(fn("alSourceUnqueueBuffers"))
alGetListenerfFunc = C.LPALGETLISTENERF(fn("alGetListenerf"))
alGetListenerfvFunc = C.LPALGETLISTENERFV(fn("alGetListenerfv"))
alListenerfFunc = C.LPALLISTENERF(fn("alListenerf"))
alListenerfvFunc = C.LPALLISTENERFV(fn("alListenerfv"))
alGenBuffersFunc = C.LPALGENBUFFERS(fn("alGenBuffers"))
alDeleteBuffersFunc = C.LPALDELETEBUFFERS(fn("alDeleteBuffers"))
alGetBufferiFunc = C.LPALGETBUFFERI(fn("alGetBufferi"))
alBufferDataFunc = C.LPALBUFFERDATA(fn("alBufferData"))
alIsBufferFunc = C.LPALISBUFFER(fn("alIsBuffer"))
alcGetErrorFunc = C.LPALCGETERROR(fn("alcGetError"))
alcOpenDeviceFunc = C.LPALCOPENDEVICE(fn("alcOpenDevice"))
alcCloseDeviceFunc = C.LPALCCLOSEDEVICE(fn("alcCloseDevice"))
alcCreateContextFunc = C.LPALCCREATECONTEXT(fn("alcCreateContext"))
alcMakeContextCurrentFunc = C.LPALCMAKECONTEXTCURRENT(fn("alcMakeContextCurrent"))
alcDestroyContextFunc = C.LPALCDESTROYCONTEXT(fn("alcDestroyContext"))
}
func fn(fname string) unsafe.Pointer {
name := C.CString(fname)
defer C.free(unsafe.Pointer(name))
p := C.dlsym(alHandle, name)
if uintptr(p) == 0 {
log.Fatalf("al: couldn't dlsym %q", fname)
}
return p
}
func alEnable(capability int32) {
C.call_alEnable(alEnableFunc, C.ALenum(capability))
}
func alDisable(capability int32) {
C.call_alDisable(alDisableFunc, C.ALenum(capability))
}
func alIsEnabled(capability int32) bool {
return C.call_alIsEnabled(alIsEnabledFunc, C.ALenum(capability)) == C.AL_TRUE
}
func alGetInteger(k int) int32 {
return int32(C.call_alGetInteger(alGetIntegerFunc, C.ALenum(k)))
}
func alGetIntegerv(k int, v []int32) {
C.call_alGetIntegerv(alGetIntegervFunc, C.ALenum(k), (*C.ALint)(unsafe.Pointer(&v[0])))
}
func alGetFloat(k int) float32 {
return float32(C.call_alGetFloat(alGetFloatFunc, C.ALenum(k)))
}
func alGetFloatv(k int, v []float32) {
C.call_alGetFloatv(alGetFloatvFunc, C.ALenum(k), (*C.ALfloat)(unsafe.Pointer(&v[0])))
}
func alGetBoolean(k int) bool {
return C.call_alGetBoolean(alGetBooleanFunc, C.ALenum(k)) == C.AL_TRUE
}
func alGetBooleanv(k int, v []bool) {
val := make([]C.ALboolean, len(v))
for i, bv := range v {
if bv {
val[i] = C.AL_TRUE
} else {
val[i] = C.AL_FALSE
}
}
C.call_alGetBooleanv(alGetBooleanvFunc, C.ALenum(k), &val[0])
}
func alGetString(v int) string {
value := C.call_alGetString(alGetStringFunc, C.ALenum(v))
return C.GoString(value)
}
func alDistanceModel(v int32) {
C.call_alDistanceModel(alDistanceModelFunc, C.ALenum(v))
}
func alDopplerFactor(v float32) {
C.call_alDopplerFactor(alDopplerFactorFunc, C.ALfloat(v))
}
func alDopplerVelocity(v float32) {
C.call_alDopplerVelocity(alDopplerVelocityFunc, C.ALfloat(v))
}
func alSpeedOfSound(v float32) {
C.call_alSpeedOfSound(alSpeedOfSoundFunc, C.ALfloat(v))
}
func alGetError() int32 {
return int32(C.call_alGetError(alGetErrorFunc))
}
func alGenSources(n int) []Source {
s := make([]Source, n)
C.call_alGenSources(alGenSourcesFunc, C.ALsizei(n), (*C.ALuint)(unsafe.Pointer(&s[0])))
return s
}
func alSourcePlayv(s []Source) {
C.call_alSourcePlayv(alSourcePlayvFunc, C.ALsizei(len(s)), (*C.ALuint)(unsafe.Pointer(&s[0])))
}
func alSourcePausev(s []Source) {
C.call_alSourcePausev(alSourcePausevFunc, C.ALsizei(len(s)), (*C.ALuint)(unsafe.Pointer(&s[0])))
}
func alSourceStopv(s []Source) {
C.call_alSourceStopv(alSourceStopvFunc, C.ALsizei(len(s)), (*C.ALuint)(unsafe.Pointer(&s[0])))
}
func alSourceRewindv(s []Source) {
C.call_alSourceRewindv(alSourceRewindvFunc, C.ALsizei(len(s)), (*C.ALuint)(unsafe.Pointer(&s[0])))
}
func alDeleteSources(s []Source) {
C.call_alDeleteSources(alDeleteSourcesFunc, C.ALsizei(len(s)), (*C.ALuint)(unsafe.Pointer(&s[0])))
}
func alGetSourcei(s Source, k int) int32 {
var v C.ALint
C.call_alGetSourcei(alGetSourceiFunc, C.ALuint(s), C.ALenum(k), &v)
return int32(v)
}
func alGetSourcef(s Source, k int) float32 {
var v C.ALfloat
C.call_alGetSourcef(alGetSourcefFunc, C.ALuint(s), C.ALenum(k), &v)
return float32(v)
}
func alGetSourcefv(s Source, k int, v []float32) {
C.call_alGetSourcefv(alGetSourcefvFunc, C.ALuint(s), C.ALenum(k), (*C.ALfloat)(unsafe.Pointer(&v[0])))
}
func alSourcei(s Source, k int, v int32) {
C.call_alSourcei(alSourcefFunc, C.ALuint(s), C.ALenum(k), C.ALint(v))
}
func alSourcef(s Source, k int, v float32) {
C.call_alSourcef(alSourcefFunc, C.ALuint(s), C.ALenum(k), C.ALfloat(v))
}
func alSourcefv(s Source, k int, v []float32) {
C.call_alSourcefv(alSourcefvFunc, C.ALuint(s), C.ALenum(k), (*C.ALfloat)(unsafe.Pointer(&v[0])))
}
func alSourceQueueBuffers(s Source, b []Buffer) {
C.call_alSourceQueueBuffers(alSourceQueueBuffersFunc, C.ALuint(s), C.ALsizei(len(b)), (*C.ALuint)(unsafe.Pointer(&b[0])))
}
func alSourceUnqueueBuffers(s Source, b []Buffer) {
C.call_alSourceUnqueueBuffers(alSourceUnqueueBuffersFunc, C.ALuint(s), C.ALsizei(len(b)), (*C.ALuint)(unsafe.Pointer(&b[0])))
}
func alGetListenerf(k int) float32 {
var v C.ALfloat
C.call_alGetListenerf(alListenerfFunc, C.ALenum(k), &v)
return float32(v)
}
func alGetListenerfv(k int, v []float32) {
C.call_alGetListenerfv(alGetListenerfvFunc, C.ALenum(k), (*C.ALfloat)(unsafe.Pointer(&v[0])))
}
func alListenerf(k int, v float32) {
C.call_alListenerf(alListenerfFunc, C.ALenum(k), C.ALfloat(v))
}
func alListenerfv(k int, v []float32) {
C.call_alListenerfv(alListenerfvFunc, C.ALenum(k), (*C.ALfloat)(unsafe.Pointer(&v[0])))
}
func alGenBuffers(n int) []Buffer {
s := make([]Buffer, n)
C.call_alGenBuffers(alGenBuffersFunc, C.ALsizei(n), (*C.ALuint)(unsafe.Pointer(&s[0])))
return s
}
func alDeleteBuffers(b []Buffer) {
C.call_alDeleteBuffers(alDeleteBuffersFunc, C.ALsizei(len(b)), (*C.ALuint)(unsafe.Pointer(&b[0])))
}
func alGetBufferi(b Buffer, k int) int32 {
var v C.ALint
C.call_alGetBufferi(alGetBufferiFunc, C.ALuint(b), C.ALenum(k), &v)
return int32(v)
}
func alBufferData(b Buffer, format uint32, data []byte, freq int32) {
C.call_alBufferData(alBufferDataFunc, C.ALuint(b), C.ALenum(format), unsafe.Pointer(&data[0]), C.ALsizei(len(data)), C.ALsizei(freq))
}
func alIsBuffer(b Buffer) bool {
return C.call_alIsBuffer(alIsBufferFunc, C.ALuint(b)) == C.AL_TRUE
}

View file

@ -0,0 +1,208 @@
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build darwin || (linux && !android) || windows
package al
/*
#cgo darwin CFLAGS: -DGOOS_darwin
#cgo linux CFLAGS: -DGOOS_linux
#cgo windows CFLAGS: -DGOOS_windows
#cgo darwin LDFLAGS: -framework OpenAL
#cgo linux LDFLAGS: -lopenal
#cgo windows LDFLAGS: -lOpenAL32
#ifdef GOOS_darwin
#include <stdlib.h>
#include <OpenAL/al.h>
#endif
#ifdef GOOS_linux
#include <stdlib.h>
#include <AL/al.h> // install on Ubuntu with: sudo apt-get install libopenal-dev
#endif
#ifdef GOOS_windows
#include <windows.h>
#include <stdlib.h>
#include <AL/al.h>
#endif
*/
import "C"
import "unsafe"
func alEnable(capability int32) {
C.alEnable(C.ALenum(capability))
}
func alDisable(capability int32) {
C.alDisable(C.ALenum(capability))
}
func alIsEnabled(capability int32) bool {
return C.alIsEnabled(C.ALenum(capability)) == C.AL_TRUE
}
func alGetInteger(k int) int32 {
return int32(C.alGetInteger(C.ALenum(k)))
}
func alGetIntegerv(k int, v []int32) {
C.alGetIntegerv(C.ALenum(k), (*C.ALint)(unsafe.Pointer(&v[0])))
}
func alGetFloat(k int) float32 {
return float32(C.alGetFloat(C.ALenum(k)))
}
func alGetFloatv(k int, v []float32) {
C.alGetFloatv(C.ALenum(k), (*C.ALfloat)(unsafe.Pointer(&v[0])))
}
func alGetBoolean(k int) bool {
return C.alGetBoolean(C.ALenum(k)) == C.AL_TRUE
}
func alGetBooleanv(k int, v []bool) {
val := make([]C.ALboolean, len(v))
for i, bv := range v {
if bv {
val[i] = C.AL_TRUE
} else {
val[i] = C.AL_FALSE
}
}
C.alGetBooleanv(C.ALenum(k), &val[0])
}
func alGetString(v int) string {
value := C.alGetString(C.ALenum(v))
return C.GoString((*C.char)(value))
}
func alDistanceModel(v int32) {
C.alDistanceModel(C.ALenum(v))
}
func alDopplerFactor(v float32) {
C.alDopplerFactor(C.ALfloat(v))
}
func alDopplerVelocity(v float32) {
C.alDopplerVelocity(C.ALfloat(v))
}
func alSpeedOfSound(v float32) {
C.alSpeedOfSound(C.ALfloat(v))
}
func alGetError() int32 {
return int32(C.alGetError())
}
func alGenSources(n int) []Source {
s := make([]Source, n)
C.alGenSources(C.ALsizei(n), (*C.ALuint)(unsafe.Pointer(&s[0])))
return s
}
func alSourcePlayv(s []Source) {
C.alSourcePlayv(C.ALsizei(len(s)), (*C.ALuint)(unsafe.Pointer(&s[0])))
}
func alSourcePausev(s []Source) {
C.alSourcePausev(C.ALsizei(len(s)), (*C.ALuint)(unsafe.Pointer(&s[0])))
}
func alSourceStopv(s []Source) {
C.alSourceStopv(C.ALsizei(len(s)), (*C.ALuint)(unsafe.Pointer(&s[0])))
}
func alSourceRewindv(s []Source) {
C.alSourceRewindv(C.ALsizei(len(s)), (*C.ALuint)(unsafe.Pointer(&s[0])))
}
func alDeleteSources(s []Source) {
C.alDeleteSources(C.ALsizei(len(s)), (*C.ALuint)(unsafe.Pointer(&s[0])))
}
func alGetSourcei(s Source, k int) int32 {
var v C.ALint
C.alGetSourcei(C.ALuint(s), C.ALenum(k), &v)
return int32(v)
}
func alGetSourcef(s Source, k int) float32 {
var v C.ALfloat
C.alGetSourcef(C.ALuint(s), C.ALenum(k), &v)
return float32(v)
}
func alGetSourcefv(s Source, k int, v []float32) {
C.alGetSourcefv(C.ALuint(s), C.ALenum(k), (*C.ALfloat)(unsafe.Pointer(&v[0])))
}
func alSourcei(s Source, k int, v int32) {
C.alSourcei(C.ALuint(s), C.ALenum(k), C.ALint(v))
}
func alSourcef(s Source, k int, v float32) {
C.alSourcef(C.ALuint(s), C.ALenum(k), C.ALfloat(v))
}
func alSourcefv(s Source, k int, v []float32) {
C.alSourcefv(C.ALuint(s), C.ALenum(k), (*C.ALfloat)(unsafe.Pointer(&v[0])))
}
func alSourceQueueBuffers(s Source, b []Buffer) {
C.alSourceQueueBuffers(C.ALuint(s), C.ALsizei(len(b)), (*C.ALuint)(unsafe.Pointer(&b[0])))
}
func alSourceUnqueueBuffers(s Source, b []Buffer) {
C.alSourceUnqueueBuffers(C.ALuint(s), C.ALsizei(len(b)), (*C.ALuint)(unsafe.Pointer(&b[0])))
}
func alGetListenerf(k int) float32 {
var v C.ALfloat
C.alGetListenerf(C.ALenum(k), &v)
return float32(v)
}
func alGetListenerfv(k int, v []float32) {
C.alGetListenerfv(C.ALenum(k), (*C.ALfloat)(unsafe.Pointer(&v[0])))
}
func alListenerf(k int, v float32) {
C.alListenerf(C.ALenum(k), C.ALfloat(v))
}
func alListenerfv(k int, v []float32) {
C.alListenerfv(C.ALenum(k), (*C.ALfloat)(unsafe.Pointer(&v[0])))
}
func alGenBuffers(n int) []Buffer {
s := make([]Buffer, n)
C.alGenBuffers(C.ALsizei(n), (*C.ALuint)(unsafe.Pointer(&s[0])))
return s
}
func alDeleteBuffers(b []Buffer) {
C.alDeleteBuffers(C.ALsizei(len(b)), (*C.ALuint)(unsafe.Pointer(&b[0])))
}
func alGetBufferi(b Buffer, k int) int32 {
var v C.ALint
C.alGetBufferi(C.ALuint(b), C.ALenum(k), &v)
return int32(v)
}
func alBufferData(b Buffer, format uint32, data []byte, freq int32) {
C.alBufferData(C.ALuint(b), C.ALenum(format), unsafe.Pointer(&data[0]), C.ALsizei(len(data)), C.ALsizei(freq))
}
func alIsBuffer(b Buffer) bool {
return C.alIsBuffer(C.ALuint(b)) == C.AL_TRUE
}

74
exp/audio/al/alc.go Normal file
View file

@ -0,0 +1,74 @@
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build darwin || linux || windows
package al
import (
"errors"
"sync"
"unsafe"
)
var (
mu sync.Mutex
device unsafe.Pointer
context unsafe.Pointer
)
// DeviceError returns the last known error from the current device.
func DeviceError() int32 {
return alcGetError(device)
}
// TODO(jbd): Investigate the cases where multiple audio output
// devices might be needed.
// OpenDevice opens the default audio device.
// Calls to OpenDevice are safe for concurrent use.
func OpenDevice() error {
mu.Lock()
defer mu.Unlock()
// already opened
if device != nil {
return nil
}
dev := alcOpenDevice("")
if dev == nil {
return errors.New("al: cannot open the default audio device")
}
ctx := alcCreateContext(dev, nil)
if ctx == nil {
alcCloseDevice(dev)
return errors.New("al: cannot create a new context")
}
if !alcMakeContextCurrent(ctx) {
alcCloseDevice(dev)
return errors.New("al: cannot make context current")
}
device = dev
context = ctx
return nil
}
// CloseDevice closes the device and frees related resources.
// Calls to CloseDevice are safe for concurrent use.
func CloseDevice() {
mu.Lock()
defer mu.Unlock()
if device == nil {
return
}
alcCloseDevice(device)
if context != nil {
alcDestroyContext(context)
}
device = nil
context = nil
}

View file

@ -0,0 +1,76 @@
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package al
/*
#include <stdlib.h>
#include <dlfcn.h>
#include <AL/al.h>
#include <AL/alc.h>
ALCint call_alcGetError(LPALCGETERROR fn, ALCdevice* d) {
return fn(d);
}
ALCdevice* call_alcOpenDevice(LPALCOPENDEVICE fn, const ALCchar* name) {
return fn(name);
}
ALCboolean call_alcCloseDevice(LPALCCLOSEDEVICE fn, ALCdevice* d) {
return fn(d);
}
ALCcontext* call_alcCreateContext(LPALCCREATECONTEXT fn, ALCdevice* d, const ALCint* attrs) {
return fn(d, attrs);
}
ALCboolean call_alcMakeContextCurrent(LPALCMAKECONTEXTCURRENT fn, ALCcontext* c) {
return fn(c);
}
void call_alcDestroyContext(LPALCDESTROYCONTEXT fn, ALCcontext* c) {
return fn(c);
}
*/
import "C"
import (
"sync"
"unsafe"
)
var once sync.Once
func alcGetError(d unsafe.Pointer) int32 {
dev := (*C.ALCdevice)(d)
return int32(C.call_alcGetError(alcGetErrorFunc, dev))
}
func alcOpenDevice(name string) unsafe.Pointer {
once.Do(initAL)
n := C.CString(name)
defer C.free(unsafe.Pointer(n))
return (unsafe.Pointer)(C.call_alcOpenDevice(alcOpenDeviceFunc, (*C.ALCchar)(unsafe.Pointer(n))))
}
func alcCloseDevice(d unsafe.Pointer) bool {
dev := (*C.ALCdevice)(d)
return C.call_alcCloseDevice(alcCloseDeviceFunc, dev) == C.AL_TRUE
}
func alcCreateContext(d unsafe.Pointer, attrs []int32) unsafe.Pointer {
dev := (*C.ALCdevice)(d)
// TODO(jbd): Handle attrs.
return (unsafe.Pointer)(C.call_alcCreateContext(alcCreateContextFunc, dev, nil))
}
func alcMakeContextCurrent(c unsafe.Pointer) bool {
ctx := (*C.ALCcontext)(c)
return C.call_alcMakeContextCurrent(alcMakeContextCurrentFunc, ctx) == C.AL_TRUE
}
func alcDestroyContext(c unsafe.Pointer) {
C.call_alcDestroyContext(alcDestroyContextFunc, (*C.ALCcontext)(c))
}

View file

@ -0,0 +1,70 @@
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build darwin || (linux && !android) || windows
package al
/*
#cgo darwin CFLAGS: -DGOOS_darwin
#cgo linux CFLAGS: -DGOOS_linux
#cgo windows CFLAGS: -DGOOS_windows
#cgo darwin LDFLAGS: -framework OpenAL
#cgo linux LDFLAGS: -lopenal
#cgo windows LDFLAGS: -lOpenAL32
#ifdef GOOS_darwin
#include <stdlib.h>
#include <OpenAL/alc.h>
#endif
#ifdef GOOS_linux
#include <stdlib.h>
#include <AL/alc.h>
#endif
#ifdef GOOS_windows
#include <windows.h>
#include <stdlib.h>
#include <AL/alc.h>
#endif
*/
import "C"
import "unsafe"
/*
On Ubuntu 14.04 'Trusty', you may have to install these libraries:
sudo apt-get install libopenal-dev
*/
func alcGetError(d unsafe.Pointer) int32 {
dev := (*C.ALCdevice)(d)
return int32(C.alcGetError(dev))
}
func alcOpenDevice(name string) unsafe.Pointer {
n := C.CString(name)
defer C.free(unsafe.Pointer(n))
return (unsafe.Pointer)(C.alcOpenDevice((*C.ALCchar)(unsafe.Pointer(n))))
}
func alcCloseDevice(d unsafe.Pointer) bool {
dev := (*C.ALCdevice)(d)
return C.alcCloseDevice(dev) == C.ALC_TRUE
}
func alcCreateContext(d unsafe.Pointer, attrs []int32) unsafe.Pointer {
dev := (*C.ALCdevice)(d)
return (unsafe.Pointer)(C.alcCreateContext(dev, nil))
}
func alcMakeContextCurrent(c unsafe.Pointer) bool {
ctx := (*C.ALCcontext)(c)
return C.alcMakeContextCurrent(ctx) == C.ALC_TRUE
}
func alcDestroyContext(c unsafe.Pointer) {
C.alcDestroyContext((*C.ALCcontext)(c))
}

82
exp/audio/al/const.go Normal file
View file

@ -0,0 +1,82 @@
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build darwin || linux || windows
package al
// Error returns one of these error codes.
const (
InvalidName = 0xA001
InvalidEnum = 0xA002
InvalidValue = 0xA003
InvalidOperation = 0xA004
OutOfMemory = 0xA005
)
// Distance models.
const (
InverseDistance = 0xD001
InverseDistanceClamped = 0xD002
LinearDistance = 0xD003
LinearDistanceClamped = 0xD004
ExponentDistance = 0xD005
ExponentDistanceClamped = 0xD006
)
// Global parameters.
const (
paramDistanceModel = 0xD000
paramDopplerFactor = 0xC000
paramDopplerVelocity = 0xC001
paramSpeedOfSound = 0xC003
paramVendor = 0xB001
paramVersion = 0xB002
paramRenderer = 0xB003
paramExtensions = 0xB004
)
// Source and listener parameters.
const (
paramGain = 0x100A
paramPosition = 0x1004
paramVelocity = 0x1006
paramOrientation = 0x100F
paramMinGain = 0x100D
paramMaxGain = 0x100E
paramSourceState = 0x1010
paramBuffersQueued = 0x1015
paramBuffersProcessed = 0x1016
paramSecOffset = 0x1024
paramSampleOffset = 0x1025
paramByteOffset = 0x1026
)
// A source could be in the state of initial, playing, paused or stopped.
const (
Initial = 0x1011
Playing = 0x1012
Paused = 0x1013
Stopped = 0x1014
)
// Buffer parameters.
const (
paramFreq = 0x2001
paramBits = 0x2002
paramChannels = 0x2003
paramSize = 0x2004
)
// Audio formats. Buffer.BufferData accepts one of these formats as the data format.
const (
FormatMono8 = 0x1100
FormatMono16 = 0x1101
FormatStereo8 = 0x1102
FormatStereo16 = 0x1103
)
// CapabilityDistanceModel represents the capability of specifying a different distance
// model for each source.
const CapabilityDistanceModel = Capability(0x200)

109
exp/f32/affine.go Normal file
View file

@ -0,0 +1,109 @@
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package f32
import "fmt"
// An Affine is a 3x3 matrix of float32 values for which the bottom row is
// implicitly always equal to [0 0 1].
// Elements are indexed first by row then column, i.e. m[row][column].
type Affine [2]Vec3
func (m Affine) String() string {
return fmt.Sprintf(`Affine[% 0.3f, % 0.3f, % 0.3f,
% 0.3f, % 0.3f, % 0.3f]`,
m[0][0], m[0][1], m[0][2],
m[1][0], m[1][1], m[1][2])
}
// Identity sets m to be the identity transform.
func (m *Affine) Identity() {
*m = Affine{
{1, 0, 0},
{0, 1, 0},
}
}
// Eq reports whether each component of m is within epsilon of the same
// component in n.
func (m *Affine) Eq(n *Affine, epsilon float32) bool {
for i := range m {
for j := range m[i] {
diff := m[i][j] - n[i][j]
if diff < -epsilon || +epsilon < diff {
return false
}
}
}
return true
}
// Mul sets m to be p × q.
func (m *Affine) Mul(p, q *Affine) {
// Store the result in local variables, in case m == a || m == b.
m00 := p[0][0]*q[0][0] + p[0][1]*q[1][0]
m01 := p[0][0]*q[0][1] + p[0][1]*q[1][1]
m02 := p[0][0]*q[0][2] + p[0][1]*q[1][2] + p[0][2]
m10 := p[1][0]*q[0][0] + p[1][1]*q[1][0]
m11 := p[1][0]*q[0][1] + p[1][1]*q[1][1]
m12 := p[1][0]*q[0][2] + p[1][1]*q[1][2] + p[1][2]
m[0][0] = m00
m[0][1] = m01
m[0][2] = m02
m[1][0] = m10
m[1][1] = m11
m[1][2] = m12
}
// Inverse sets m to be the inverse of p.
func (m *Affine) Inverse(p *Affine) {
m00 := p[1][1]
m01 := -p[0][1]
m02 := p[1][2]*p[0][1] - p[1][1]*p[0][2]
m10 := -p[1][0]
m11 := p[0][0]
m12 := p[1][0]*p[0][2] - p[1][2]*p[0][0]
det := m00*m11 - m10*m01
m[0][0] = m00 / det
m[0][1] = m01 / det
m[0][2] = m02 / det
m[1][0] = m10 / det
m[1][1] = m11 / det
m[1][2] = m12 / det
}
// Scale sets m to be a scale followed by p.
// It is equivalent to m.Mul(p, &Affine{{x,0,0}, {0,y,0}}).
func (m *Affine) Scale(p *Affine, x, y float32) {
m[0][0] = p[0][0] * x
m[0][1] = p[0][1] * y
m[0][2] = p[0][2]
m[1][0] = p[1][0] * x
m[1][1] = p[1][1] * y
m[1][2] = p[1][2]
}
// Translate sets m to be a translation followed by p.
// It is equivalent to m.Mul(p, &Affine{{1,0,x}, {0,1,y}}).
func (m *Affine) Translate(p *Affine, x, y float32) {
m[0][0] = p[0][0]
m[0][1] = p[0][1]
m[0][2] = p[0][0]*x + p[0][1]*y + p[0][2]
m[1][0] = p[1][0]
m[1][1] = p[1][1]
m[1][2] = p[1][0]*x + p[1][1]*y + p[1][2]
}
// Rotate sets m to a rotation in radians followed by p.
// It is equivalent to m.Mul(p, affineRotation).
func (m *Affine) Rotate(p *Affine, radians float32) {
s, c := Sin(radians), Cos(radians)
m.Mul(p, &Affine{
{+c, +s, 0},
{-s, +c, 0},
})
}

140
exp/f32/affine_test.go Normal file
View file

@ -0,0 +1,140 @@
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package f32
import (
"math"
"testing"
)
var xyTests = []struct {
x, y float32
}{
{0, 0},
{1, 1},
{2, 3},
{6.5, 4.3},
}
var a = Affine{
{3, 4, 5},
{6, 7, 8},
}
func TestInverse(t *testing.T) {
wantInv := Affine{
{-2.33333, 1.33333, 1},
{2, -1, -2},
}
var gotInv Affine
gotInv.Inverse(&a)
if !gotInv.Eq(&wantInv, 0.01) {
t.Errorf("Inverse: got %s want %s", gotInv, wantInv)
}
var wantId, gotId Affine
wantId.Identity()
gotId.Mul(&a, &wantInv)
if !gotId.Eq(&wantId, 0.01) {
t.Errorf("Identity #0: got %s want %s", gotId, wantId)
}
gotId.Mul(&wantInv, &a)
if !gotId.Eq(&wantId, 0.01) {
t.Errorf("Identity #1: got %s want %s", gotId, wantId)
}
}
func TestAffineScale(t *testing.T) {
for _, test := range xyTests {
want := a
want.Mul(&want, &Affine{{test.x, 0, 0}, {0, test.y, 0}})
got := a
got.Scale(&got, test.x, test.y)
if !got.Eq(&want, 0.01) {
t.Errorf("(%.2f, %.2f): got %s want %s", test.x, test.y, got, want)
}
}
}
func TestAffineTranslate(t *testing.T) {
for _, test := range xyTests {
want := a
want.Mul(&want, &Affine{{1, 0, test.x}, {0, 1, test.y}})
got := a
got.Translate(&got, test.x, test.y)
if !got.Eq(&want, 0.01) {
t.Errorf("(%.2f, %.2f): got %s want %s", test.x, test.y, got, want)
}
}
}
func TestAffineRotate(t *testing.T) {
want := Affine{
{-4.000, 3.000, 5.000},
{-7.000, 6.000, 8.000},
}
got := a
got.Rotate(&got, math.Pi/2)
if !got.Eq(&want, 0.01) {
t.Errorf("rotate π: got %s want %s", got, want)
}
want = a
got = a
got.Rotate(&got, 2*math.Pi)
if !got.Eq(&want, 0.01) {
t.Errorf("rotate 2π: got %s want %s", got, want)
}
got = a
got.Rotate(&got, math.Pi)
got.Rotate(&got, math.Pi)
if !got.Eq(&want, 0.01) {
t.Errorf("rotate π then π: got %s want %s", got, want)
}
got = a
got.Rotate(&got, math.Pi/3)
got.Rotate(&got, -math.Pi/3)
if !got.Eq(&want, 0.01) {
t.Errorf("rotate π/3 then -π/3: got %s want %s", got, want)
}
}
func TestAffineScaleTranslate(t *testing.T) {
mulVec := func(m *Affine, v [2]float32) (mv [2]float32) {
mv[0] = m[0][0]*v[0] + m[0][1]*v[1] + m[0][2]
mv[1] = m[1][0]*v[0] + m[1][1]*v[1] + m[1][2]
return mv
}
v := [2]float32{1, 10}
var sThenT Affine
sThenT.Identity()
sThenT.Scale(&sThenT, 13, 17)
sThenT.Translate(&sThenT, 101, 151)
wantSTT := [2]float32{
13 * (101 + 1),
17 * (151 + 10),
}
if got := mulVec(&sThenT, v); got != wantSTT {
t.Errorf("S then T: got %v, want %v", got, wantSTT)
}
var tThenS Affine
tThenS.Identity()
tThenS.Translate(&tThenS, 101, 151)
tThenS.Scale(&tThenS, 13, 17)
wantTTS := [2]float32{
101 + (13 * 1),
151 + (17 * 10),
}
if got := mulVec(&tThenS, v); got != wantTTS {
t.Errorf("T then S: got %v, want %v", got, wantTTS)
}
}

93
exp/f32/f32.go Normal file
View file

@ -0,0 +1,93 @@
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:generate go run gen.go -output table.go
// Package f32 implements some linear algebra and GL helpers for float32s.
//
// Types defined in this package have methods implementing common
// mathematical operations. The common form for these functions is
//
// func (dst *T) Op(lhs, rhs *T)
//
// which reads in traditional mathematical notation as
//
// dst = lhs op rhs.
//
// It is safe to use the destination address as the left-hand side,
// that is, dst *= rhs is dst.Mul(dst, rhs).
//
// # WARNING
//
// The interface to this package is not stable. It will change considerably.
// Only use functions that provide package documentation. Semantics are
// non-obvious. Be prepared for the package name to change.
package f32
import (
"encoding/binary"
"fmt"
"math"
)
type Radian float32
func Cos(x float32) float32 {
const n = sinTableLen
i := uint32(int32(x * (n / math.Pi)))
i += n / 2
i &= 2*n - 1
if i >= n {
return -sinTable[i&(n-1)]
}
return sinTable[i&(n-1)]
}
func Sin(x float32) float32 {
const n = sinTableLen
i := uint32(int32(x * (n / math.Pi)))
i &= 2*n - 1
if i >= n {
return -sinTable[i&(n-1)]
}
return sinTable[i&(n-1)]
}
func Sqrt(x float32) float32 {
return float32(math.Sqrt(float64(x))) // TODO(crawshaw): implement
}
func Tan(x float32) float32 {
return float32(math.Tan(float64(x))) // TODO(crawshaw): fast version
}
// Bytes returns the byte representation of float32 values in the given byte
// order. byteOrder must be either binary.BigEndian or binary.LittleEndian.
func Bytes(byteOrder binary.ByteOrder, values ...float32) []byte {
le := false
switch byteOrder {
case binary.BigEndian:
case binary.LittleEndian:
le = true
default:
panic(fmt.Sprintf("invalid byte order %v", byteOrder))
}
b := make([]byte, 4*len(values))
for i, v := range values {
u := math.Float32bits(v)
if le {
b[4*i+0] = byte(u >> 0)
b[4*i+1] = byte(u >> 8)
b[4*i+2] = byte(u >> 16)
b[4*i+3] = byte(u >> 24)
} else {
b[4*i+0] = byte(u >> 24)
b[4*i+1] = byte(u >> 16)
b[4*i+2] = byte(u >> 8)
b[4*i+3] = byte(u >> 0)
}
}
return b
}

361
exp/f32/f32_test.go Normal file
View file

@ -0,0 +1,361 @@
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package f32
import (
"bytes"
"encoding/binary"
"math"
"testing"
)
func TestAffineTranslationsCommute(t *testing.T) {
a := &Affine{
{1, 0, 3},
{0, 1, 4},
}
b := &Affine{
{1, 0, 20},
{0, 1, 30},
}
var m0, m1 Affine
m0.Mul(a, b)
m1.Mul(b, a)
if !m0.Eq(&m1, 0) {
t.Errorf("m0, m1 differ.\nm0: %v\nm1: %v", m0, m1)
}
}
func TestAffineMat3Equivalence(t *testing.T) {
a0 := Affine{
{13, 19, 37},
{101, 149, 311},
}
m0 := Mat3{
a0[0],
a0[1],
{0, 0, 1},
}
a1 := Affine{
{1009, 1051, 1087},
{563, 569, 571},
}
m1 := Mat3{
a1[0],
a1[1],
{0, 0, 1},
}
a2 := Affine{}
a2.Mul(&a0, &a1)
m2 := Mat3{
a2[0],
a2[1],
{0, 0, 1},
}
mm := Mat3{}
mm.Mul(&m0, &m1)
if !m2.Eq(&mm, 0) {
t.Errorf("m2, mm differ.\nm2: %v\nmm: %v", m2, mm)
}
}
var x3 = Mat3{
{0, 1, 2},
{3, 4, 5},
{6, 7, 8},
}
var x3sq = Mat3{
{15, 18, 21},
{42, 54, 66},
{69, 90, 111},
}
var id3 = Mat3{
{1, 0, 0},
{0, 1, 0},
{0, 0, 1},
}
func TestMat3Mul(t *testing.T) {
tests := []struct{ m0, m1, want Mat3 }{
{x3, id3, x3},
{id3, x3, x3},
{x3, x3, x3sq},
{
Mat3{
{+1.811, +0.000, +0.000},
{+0.000, +2.414, +0.000},
{+0.000, +0.000, -1.010},
},
Mat3{
{+0.992, -0.015, +0.123},
{+0.000, +0.992, +0.123},
{-0.124, -0.122, +0.985},
},
Mat3{
{+1.797, -0.027, +0.223},
{+0.000, +2.395, +0.297},
{+0.125, +0.123, -0.995},
},
},
}
for i, test := range tests {
got := Mat3{}
got.Mul(&test.m0, &test.m1)
if !got.Eq(&test.want, 0.01) {
t.Errorf("test #%d:\n%s *\n%s =\n%s, want\n%s", i, test.m0, test.m1, got, test.want)
}
}
}
func TestMat3SelfMul(t *testing.T) {
m := x3
m.Mul(&m, &m)
if !m.Eq(&x3sq, 0) {
t.Errorf("m, x3sq differ.\nm: %v\nx3sq: %v", m, x3sq)
}
}
var x4 = Mat4{
{0, 1, 2, 3},
{4, 5, 6, 7},
{8, 9, 10, 11},
{12, 13, 14, 15},
}
var x4sq = Mat4{
{56, 62, 68, 74},
{152, 174, 196, 218},
{248, 286, 324, 362},
{344, 398, 452, 506},
}
var id4 = Mat4{
{1, 0, 0, 0},
{0, 1, 0, 0},
{0, 0, 1, 0},
{0, 0, 0, 1},
}
func TestMat4Eq(t *testing.T) {
tests := []struct {
m0, m1 Mat4
eq bool
}{
{x4, x4, true},
{id4, id4, true},
{x4, id4, false},
}
for _, test := range tests {
got := test.m0.Eq(&test.m1, 0.01)
if got != test.eq {
t.Errorf("Eq=%v, want %v for\n%s\n%s", got, test.eq, test.m0, test.m1)
}
}
}
func TestMat4Mul(t *testing.T) {
tests := []struct{ m0, m1, want Mat4 }{
{x4, id4, x4},
{id4, x4, x4},
{x4, x4, x4sq},
{
Mat4{
{+1.811, +0.000, +0.000, +0.000},
{+0.000, +2.414, +0.000, +0.000},
{+0.000, +0.000, -1.010, -1.000},
{+0.000, +0.000, -2.010, +0.000},
},
Mat4{
{+0.992, -0.015, +0.123, +0.000},
{+0.000, +0.992, +0.123, +0.000},
{-0.124, -0.122, +0.985, +0.000},
{-0.000, -0.000, -8.124, +1.000},
},
Mat4{
{+1.797, -0.027, +0.223, +0.000},
{+0.000, +2.395, +0.297, +0.000},
{+0.125, +0.123, +7.129, -1.000},
{+0.249, +0.245, -1.980, +0.000},
},
},
}
for i, test := range tests {
got := Mat4{}
got.Mul(&test.m0, &test.m1)
if !got.Eq(&test.want, 0.01) {
t.Errorf("test #%d:\n%s *\n%s =\n%s, want\n%s", i, test.m0, test.m1, got, test.want)
}
}
}
func TestMat4LookAt(t *testing.T) {
tests := []struct {
eye, center, up Vec3
want Mat4
}{
{
Vec3{1, 1, 8}, Vec3{0, 0, 0}, Vec3{0, 1, 0},
Mat4{
{0.992, -0.015, 0.123, 0.000},
{0.000, 0.992, 0.123, 0.000},
{-0.124, -0.122, 0.985, 0.000},
{-0.000, -0.000, -8.124, 1.000},
},
},
{
Vec3{4, 5, 7}, Vec3{0.1, 0.2, 0.3}, Vec3{0, -1, 0},
Mat4{
{-0.864, 0.265, 0.428, 0.000},
{0.000, -0.850, 0.526, 0.000},
{0.503, 0.455, 0.735, 0.000},
{-0.064, 0.007, -9.487, 1.000},
},
},
}
for _, test := range tests {
got := Mat4{}
got.LookAt(&test.eye, &test.center, &test.up)
if !got.Eq(&test.want, 0.01) {
t.Errorf("LookAt(%s,%s%s) =\n%s\nwant\n%s", test.eye, test.center, test.up, got, test.want)
}
}
}
func TestMat4Perspective(t *testing.T) {
want := Mat4{
{1.811, 0.000, 0.000, 0.000},
{0.000, 2.414, 0.000, 0.000},
{0.000, 0.000, -1.010, -1.000},
{0.000, 0.000, -2.010, 0.000},
}
got := Mat4{}
got.Perspective(Radian(math.Pi/4), 4.0/3, 1, 200)
if !got.Eq(&want, 0.01) {
t.Errorf("got\n%s\nwant\n%s", got, want)
}
}
func TestMat4Rotate(t *testing.T) {
want := &Mat4{
{2.000, 1.000, -0.000, 3.000},
{6.000, 5.000, -4.000, 7.000},
{10.000, 9.000, -8.000, 11.000},
{14.000, 13.000, -12.000, 15.000},
}
got := new(Mat4)
got.Rotate(&x4, Radian(math.Pi/2), &Vec3{0, 1, 0})
if !got.Eq(want, 0.01) {
t.Errorf("got\n%s\nwant\n%s", got, want)
}
}
func TestMat4Scale(t *testing.T) {
want := &Mat4{
{0 * 2, 1 * 3, 2 * 4, 3 * 1},
{4 * 2, 5 * 3, 6 * 4, 7 * 1},
{8 * 2, 9 * 3, 10 * 4, 11 * 1},
{12 * 2, 13 * 3, 14 * 4, 15 * 1},
}
got := new(Mat4)
got.Scale(&x4, 2, 3, 4)
if !got.Eq(want, 0.01) {
t.Errorf("got\n%s\nwant\n%s", got, want)
}
}
func TestMat4Translate(t *testing.T) {
want := &Mat4{
{0, 1, 2, 0*0.1 + 1*0.2 + 2*0.3 + 3*1},
{4, 5, 6, 4*0.1 + 5*0.2 + 6*0.3 + 7*1},
{8, 9, 10, 8*0.1 + 9*0.2 + 10*0.3 + 11*1},
{12, 13, 14, 12*0.1 + 13*0.2 + 14*0.3 + 15*1},
}
got := new(Mat4)
got.Translate(&x4, 0.1, 0.2, 0.3)
if !got.Eq(want, 0.01) {
t.Errorf("got\n%s\nwant\n%s", got, want)
}
}
func testTrig(t *testing.T, gotFunc func(float32) float32, wantFunc func(float64) float64) {
nBad := 0
for a := float32(-9); a < +9; a += .01 {
got := gotFunc(a)
want := float32(wantFunc(float64(a)))
diff := got - want
if diff < 0 {
diff = -diff
}
if diff > 0.001 {
if nBad++; nBad == 10 {
t.Errorf("too many failures")
break
}
t.Errorf("a=%+.2f: got %+.4f, want %+.4f, diff=%.4f", a, got, want, diff)
}
}
}
func TestCos(t *testing.T) { testTrig(t, Cos, math.Cos) }
func TestSin(t *testing.T) { testTrig(t, Sin, math.Sin) }
func TestTan(t *testing.T) { testTrig(t, Tan, math.Tan) }
func BenchmarkSin(b *testing.B) {
for i := 0; i < b.N; i++ {
for a := 0; a < 3141; a++ {
Sin(float32(a) / 1000)
}
}
}
func TestBytes(t *testing.T) {
testCases := []struct {
byteOrder binary.ByteOrder
want []byte
}{{
binary.BigEndian,
[]byte{
// The IEEE 754 binary32 format is 1 sign bit, 8 exponent bits and 23 fraction bits.
0x00, 0x00, 0x00, 0x00, // float32(+0.00) is 0 0000000_0 0000000_00000000_00000000
0x3f, 0xa0, 0x00, 0x00, // float32(+1.25) is 0 0111111_1 0100000_00000000_00000000
0xc0, 0x00, 0x00, 0x00, // float32(-2.00) is 1 1000000_0 0000000_00000000_00000000
},
}, {
binary.LittleEndian,
[]byte{
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0xa0, 0x3f,
0x00, 0x00, 0x00, 0xc0,
},
}}
for _, tc := range testCases {
got := Bytes(tc.byteOrder, +0.00, +1.25, -2.00)
if !bytes.Equal(got, tc.want) {
t.Errorf("%v:\ngot % x\nwant % x", tc.byteOrder, got, tc.want)
}
}
}

48
exp/f32/gen.go Normal file
View file

@ -0,0 +1,48 @@
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build ignore
package main
/*
This program generates table.go. Invoke it as:
go run gen.go -output table.go
*/
import (
"bytes"
"flag"
"fmt"
"go/format"
"log"
"math"
"os"
)
// N is the number of entries in the sin look-up table. It must be a power of 2.
const N = 4096
var filename = flag.String("output", "table.go", "output file name")
func main() {
b := new(bytes.Buffer)
fmt.Fprintf(b, "// Code generated by go run gen.go; DO NOT EDIT\n\npackage f32\n\n")
fmt.Fprintf(b, "const sinTableLen = %d\n\n", N)
fmt.Fprintf(b, "// sinTable[i] equals sin(i * π / sinTableLen).\n")
fmt.Fprintf(b, "var sinTable = [sinTableLen]float32{\n")
for i := 0; i < N; i++ {
radians := float64(i) * (math.Pi / N)
fmt.Fprintf(b, "%v,\n", float32(math.Sin(radians)))
}
fmt.Fprintf(b, "}\n")
data, err := format.Source(b.Bytes())
if err != nil {
log.Fatal(err)
}
if err := os.WriteFile(*filename, data, 0644); err != nil {
log.Fatal(err)
}
}

63
exp/f32/mat3.go Normal file
View file

@ -0,0 +1,63 @@
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package f32
import "fmt"
// A Mat3 is a 3x3 matrix of float32 values.
// Elements are indexed first by row then column, i.e. m[row][column].
type Mat3 [3]Vec3
func (m Mat3) String() string {
return fmt.Sprintf(`Mat3[% 0.3f, % 0.3f, % 0.3f,
% 0.3f, % 0.3f, % 0.3f,
% 0.3f, % 0.3f, % 0.3f]`,
m[0][0], m[0][1], m[0][2],
m[1][0], m[1][1], m[1][2],
m[2][0], m[2][1], m[2][2])
}
func (m *Mat3) Identity() {
*m = Mat3{
{1, 0, 0},
{0, 1, 0},
{0, 0, 1},
}
}
func (m *Mat3) Eq(n *Mat3, epsilon float32) bool {
for i := range m {
for j := range m[i] {
diff := m[i][j] - n[i][j]
if diff < -epsilon || +epsilon < diff {
return false
}
}
}
return true
}
// Mul stores a × b in m.
func (m *Mat3) Mul(a, b *Mat3) {
// Store the result in local variables, in case m == a || m == b.
m00 := a[0][0]*b[0][0] + a[0][1]*b[1][0] + a[0][2]*b[2][0]
m01 := a[0][0]*b[0][1] + a[0][1]*b[1][1] + a[0][2]*b[2][1]
m02 := a[0][0]*b[0][2] + a[0][1]*b[1][2] + a[0][2]*b[2][2]
m10 := a[1][0]*b[0][0] + a[1][1]*b[1][0] + a[1][2]*b[2][0]
m11 := a[1][0]*b[0][1] + a[1][1]*b[1][1] + a[1][2]*b[2][1]
m12 := a[1][0]*b[0][2] + a[1][1]*b[1][2] + a[1][2]*b[2][2]
m20 := a[2][0]*b[0][0] + a[2][1]*b[1][0] + a[2][2]*b[2][0]
m21 := a[2][0]*b[0][1] + a[2][1]*b[1][1] + a[2][2]*b[2][1]
m22 := a[2][0]*b[0][2] + a[2][1]*b[1][2] + a[2][2]*b[2][2]
m[0][0] = m00
m[0][1] = m01
m[0][2] = m02
m[1][0] = m10
m[1][1] = m11
m[1][2] = m12
m[2][0] = m20
m[2][1] = m21
m[2][2] = m22
}

195
exp/f32/mat4.go Normal file
View file

@ -0,0 +1,195 @@
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package f32
import "fmt"
// A Mat4 is a 4x4 matrix of float32 values.
// Elements are indexed first by row then column, i.e. m[row][column].
type Mat4 [4]Vec4
func (m Mat4) String() string {
return fmt.Sprintf(`Mat4[% 0.3f, % 0.3f, % 0.3f, % 0.3f,
% 0.3f, % 0.3f, % 0.3f, % 0.3f,
% 0.3f, % 0.3f, % 0.3f, % 0.3f,
% 0.3f, % 0.3f, % 0.3f, % 0.3f]`,
m[0][0], m[0][1], m[0][2], m[0][3],
m[1][0], m[1][1], m[1][2], m[1][3],
m[2][0], m[2][1], m[2][2], m[2][3],
m[3][0], m[3][1], m[3][2], m[3][3])
}
func (m *Mat4) Identity() {
*m = Mat4{
{1, 0, 0, 0},
{0, 1, 0, 0},
{0, 0, 1, 0},
{0, 0, 0, 1},
}
}
func (m *Mat4) Eq(n *Mat4, epsilon float32) bool {
for i := range m {
for j := range m[i] {
diff := m[i][j] - n[i][j]
if diff < -epsilon || +epsilon < diff {
return false
}
}
}
return true
}
// Mul stores a × b in m.
func (m *Mat4) Mul(a, b *Mat4) {
// Store the result in local variables, in case m == a || m == b.
m00 := a[0][0]*b[0][0] + a[0][1]*b[1][0] + a[0][2]*b[2][0] + a[0][3]*b[3][0]
m01 := a[0][0]*b[0][1] + a[0][1]*b[1][1] + a[0][2]*b[2][1] + a[0][3]*b[3][1]
m02 := a[0][0]*b[0][2] + a[0][1]*b[1][2] + a[0][2]*b[2][2] + a[0][3]*b[3][2]
m03 := a[0][0]*b[0][3] + a[0][1]*b[1][3] + a[0][2]*b[2][3] + a[0][3]*b[3][3]
m10 := a[1][0]*b[0][0] + a[1][1]*b[1][0] + a[1][2]*b[2][0] + a[1][3]*b[3][0]
m11 := a[1][0]*b[0][1] + a[1][1]*b[1][1] + a[1][2]*b[2][1] + a[1][3]*b[3][1]
m12 := a[1][0]*b[0][2] + a[1][1]*b[1][2] + a[1][2]*b[2][2] + a[1][3]*b[3][2]
m13 := a[1][0]*b[0][3] + a[1][1]*b[1][3] + a[1][2]*b[2][3] + a[1][3]*b[3][3]
m20 := a[2][0]*b[0][0] + a[2][1]*b[1][0] + a[2][2]*b[2][0] + a[2][3]*b[3][0]
m21 := a[2][0]*b[0][1] + a[2][1]*b[1][1] + a[2][2]*b[2][1] + a[2][3]*b[3][1]
m22 := a[2][0]*b[0][2] + a[2][1]*b[1][2] + a[2][2]*b[2][2] + a[2][3]*b[3][2]
m23 := a[2][0]*b[0][3] + a[2][1]*b[1][3] + a[2][2]*b[2][3] + a[2][3]*b[3][3]
m30 := a[3][0]*b[0][0] + a[3][1]*b[1][0] + a[3][2]*b[2][0] + a[3][3]*b[3][0]
m31 := a[3][0]*b[0][1] + a[3][1]*b[1][1] + a[3][2]*b[2][1] + a[3][3]*b[3][1]
m32 := a[3][0]*b[0][2] + a[3][1]*b[1][2] + a[3][2]*b[2][2] + a[3][3]*b[3][2]
m33 := a[3][0]*b[0][3] + a[3][1]*b[1][3] + a[3][2]*b[2][3] + a[3][3]*b[3][3]
m[0][0] = m00
m[0][1] = m01
m[0][2] = m02
m[0][3] = m03
m[1][0] = m10
m[1][1] = m11
m[1][2] = m12
m[1][3] = m13
m[2][0] = m20
m[2][1] = m21
m[2][2] = m22
m[2][3] = m23
m[3][0] = m30
m[3][1] = m31
m[3][2] = m32
m[3][3] = m33
}
// Perspective sets m to be the GL perspective matrix.
func (m *Mat4) Perspective(fov Radian, aspect, near, far float32) {
t := Tan(float32(fov) / 2)
m[0][0] = 1 / (aspect * t)
m[1][1] = 1 / t
m[2][2] = -(far + near) / (far - near)
m[2][3] = -1
m[3][2] = -2 * far * near / (far - near)
}
// Scale sets m to be a scale followed by p.
// It is equivalent to
//
// m.Mul(p, &Mat4{
// {x, 0, 0, 0},
// {0, y, 0, 0},
// {0, 0, z, 0},
// {0, 0, 0, 1},
// }).
func (m *Mat4) Scale(p *Mat4, x, y, z float32) {
m[0][0] = p[0][0] * x
m[0][1] = p[0][1] * y
m[0][2] = p[0][2] * z
m[0][3] = p[0][3]
m[1][0] = p[1][0] * x
m[1][1] = p[1][1] * y
m[1][2] = p[1][2] * z
m[1][3] = p[1][3]
m[2][0] = p[2][0] * x
m[2][1] = p[2][1] * y
m[2][2] = p[2][2] * z
m[2][3] = p[2][3]
m[3][0] = p[3][0] * x
m[3][1] = p[3][1] * y
m[3][2] = p[3][2] * z
m[3][3] = p[3][3]
}
// Translate sets m to be a translation followed by p.
// It is equivalent to
//
// m.Mul(p, &Mat4{
// {1, 0, 0, x},
// {0, 1, 0, y},
// {0, 0, 1, z},
// {0, 0, 0, 1},
// }).
func (m *Mat4) Translate(p *Mat4, x, y, z float32) {
m[0][0] = p[0][0]
m[0][1] = p[0][1]
m[0][2] = p[0][2]
m[0][3] = p[0][0]*x + p[0][1]*y + p[0][2]*z + p[0][3]
m[1][0] = p[1][0]
m[1][1] = p[1][1]
m[1][2] = p[1][2]
m[1][3] = p[1][0]*x + p[1][1]*y + p[1][2]*z + p[1][3]
m[2][0] = p[2][0]
m[2][1] = p[2][1]
m[2][2] = p[2][2]
m[2][3] = p[2][0]*x + p[2][1]*y + p[2][2]*z + p[2][3]
m[3][0] = p[3][0]
m[3][1] = p[3][1]
m[3][2] = p[3][2]
m[3][3] = p[3][0]*x + p[3][1]*y + p[3][2]*z + p[3][3]
}
// Rotate sets m to a rotation in radians around a specified axis, followed by p.
// It is equivalent to m.Mul(p, affineRotation).
func (m *Mat4) Rotate(p *Mat4, angle Radian, axis *Vec3) {
a := *axis
a.Normalize()
c, s := Cos(float32(angle)), Sin(float32(angle))
d := 1 - c
m.Mul(p, &Mat4{{
c + d*a[0]*a[1],
0 + d*a[0]*a[1] + s*a[2],
0 + d*a[0]*a[1] - s*a[1],
0,
}, {
0 + d*a[1]*a[0] - s*a[2],
c + d*a[1]*a[1],
0 + d*a[1]*a[2] + s*a[0],
0,
}, {
0 + d*a[2]*a[0] + s*a[1],
0 + d*a[2]*a[1] - s*a[0],
c + d*a[2]*a[2],
0,
}, {
0, 0, 0, 1,
}})
}
func (m *Mat4) LookAt(eye, center, up *Vec3) {
f, s, u := new(Vec3), new(Vec3), new(Vec3)
*f = *center
f.Sub(f, eye)
f.Normalize()
s.Cross(f, up)
s.Normalize()
u.Cross(s, f)
*m = Mat4{
{s[0], u[0], -f[0], 0},
{s[1], u[1], -f[1], 0},
{s[2], u[2], -f[2], 0},
{-s.Dot(eye), -u.Dot(eye), +f.Dot(eye), 1},
}
}

4105
exp/f32/table.go Normal file

File diff suppressed because it is too large Load diff

49
exp/f32/vec3.go Normal file
View file

@ -0,0 +1,49 @@
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package f32
import "fmt"
type Vec3 [3]float32
func (v Vec3) String() string {
return fmt.Sprintf("Vec3[% 0.3f, % 0.3f, % 0.3f]", v[0], v[1], v[2])
}
func (v *Vec3) Normalize() {
sq := v.Dot(v)
inv := 1 / Sqrt(sq)
v[0] *= inv
v[1] *= inv
v[2] *= inv
}
func (v *Vec3) Sub(v0, v1 *Vec3) {
v[0] = v0[0] - v1[0]
v[1] = v0[1] - v1[1]
v[2] = v0[2] - v1[2]
}
func (v *Vec3) Add(v0, v1 *Vec3) {
v[0] = v0[0] + v1[0]
v[1] = v0[1] + v1[1]
v[2] = v0[2] + v1[2]
}
func (v *Vec3) Mul(v0, v1 *Vec3) {
v[0] = v0[0] * v1[0]
v[1] = v0[1] * v1[1]
v[2] = v0[2] * v1[2]
}
func (v *Vec3) Cross(v0, v1 *Vec3) {
v[0] = v0[1]*v1[2] - v0[2]*v1[1]
v[1] = v0[2]*v1[0] - v0[0]*v1[2]
v[2] = v0[0]*v1[1] - v0[1]*v1[0]
}
func (v *Vec3) Dot(v1 *Vec3) float32 {
return v[0]*v1[0] + v[1]*v1[1] + v[2]*v1[2]
}

47
exp/f32/vec4.go Normal file
View file

@ -0,0 +1,47 @@
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package f32
import "fmt"
type Vec4 [4]float32
func (v Vec4) String() string {
return fmt.Sprintf("Vec4[% 0.3f, % 0.3f, % 0.3f, % 0.3f]", v[0], v[1], v[2], v[3])
}
func (v *Vec4) Normalize() {
sq := v.Dot(v)
inv := 1 / Sqrt(sq)
v[0] *= inv
v[1] *= inv
v[2] *= inv
v[3] *= inv
}
func (v *Vec4) Sub(v0, v1 *Vec4) {
v[0] = v0[0] - v1[0]
v[1] = v0[1] - v1[1]
v[2] = v0[2] - v1[2]
v[3] = v0[3] - v1[3]
}
func (v *Vec4) Add(v0, v1 *Vec4) {
v[0] = v0[0] + v1[0]
v[1] = v0[1] + v1[1]
v[2] = v0[2] + v1[2]
v[3] = v0[3] + v1[3]
}
func (v *Vec4) Mul(v0, v1 *Vec4) {
v[0] = v0[0] * v1[0]
v[1] = v0[1] * v1[1]
v[2] = v0[2] * v1[2]
v[3] = v0[3] * v1[3]
}
func (v *Vec4) Dot(v1 *Vec4) float32 {
return v[0]*v1[0] + v[1]*v1[1] + v[2]*v1[2] + v[3]*v1[3]
}

6
exp/font/doc.go Normal file
View file

@ -0,0 +1,6 @@
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package font provides platform independent access to system fonts.
package font

27
exp/font/font.go Normal file
View file

@ -0,0 +1,27 @@
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build linux || darwin
package font
// Default returns the default system font.
// The font is encoded as a TTF.
func Default() []byte {
b, err := buildDefault()
if err != nil {
panic(err)
}
return b
}
// Monospace returns the default system fixed-pitch font.
// The font is encoded as a TTF.
func Monospace() []byte {
b, err := buildMonospace()
if err != nil {
panic(err)
}
return b
}

15
exp/font/font_android.go Normal file
View file

@ -0,0 +1,15 @@
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package font
import "os"
func buildDefault() ([]byte, error) {
return os.ReadFile("/system/fonts/DroidSans.ttf")
}
func buildMonospace() ([]byte, error) {
return os.ReadFile("/system/fonts/DroidSansMono.ttf")
}

76
exp/font/font_darwin.go Normal file
View file

@ -0,0 +1,76 @@
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package font
/*
#cgo darwin LDFLAGS: -framework CoreFoundation -framework CoreText
#include <CoreFoundation/CFArray.h>
#include <CoreFoundation/CFBase.h>
#include <CoreFoundation/CFData.h>
#include <CoreText/CTFont.h>
*/
import "C"
import "unsafe"
func buildFont(f C.CTFontRef) []byte {
ctags := C.CTFontCopyAvailableTables(f, C.kCTFontTableOptionExcludeSynthetic)
tagsCount := C.CFArrayGetCount(ctags)
var tags []uint32
var dataRefs []C.CFDataRef
var dataLens []uint32
for i := C.CFIndex(0); i < tagsCount; i++ {
tag := (C.CTFontTableTag)((uintptr)(C.CFArrayGetValueAtIndex(ctags, i)))
dataRef := C.CTFontCopyTable(f, tag, 0) // retained
tags = append(tags, uint32(tag))
dataRefs = append(dataRefs, dataRef)
dataLens = append(dataLens, uint32(C.CFDataGetLength(dataRef)))
}
totalLen := 0
for _, l := range dataLens {
totalLen += int(l)
}
// Big-endian output.
buf := make([]byte, 0, 12+16*len(tags)+totalLen)
write16 := func(x uint16) { buf = append(buf, byte(x>>8), byte(x)) }
write32 := func(x uint32) { buf = append(buf, byte(x>>24), byte(x>>16), byte(x>>8), byte(x)) }
// File format description: http://www.microsoft.com/typography/otspec/otff.htm
write32(0x00010000) // version 1.0
write16(uint16(len(tags))) // numTables
write16(0) // searchRange
write16(0) // entrySelector
write16(0) // rangeShift
// Table tags, includes offsets into following data segments.
offset := uint32(12 + 16*len(tags)) // offset starts after table tags
for i, tag := range tags {
write32(tag)
write32(0)
write32(offset)
write32(dataLens[i])
offset += dataLens[i]
}
// Data segments.
for i, dataRef := range dataRefs {
data := (*[1<<31 - 2]byte)((unsafe.Pointer)(C.CFDataGetBytePtr(dataRef)))[:dataLens[i]]
buf = append(buf, data...)
C.CFRelease(C.CFTypeRef(dataRef))
}
return buf
}
func buildDefault() ([]byte, error) {
return buildFont(C.CTFontCreateUIFontForLanguage(C.kCTFontSystemFontType, 0, 0)), nil
}
func buildMonospace() ([]byte, error) {
return buildFont(C.CTFontCreateUIFontForLanguage(C.kCTFontUserFixedPitchFontType, 0, 0)), nil
}

31
exp/font/font_linux.go Normal file
View file

@ -0,0 +1,31 @@
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build !android
package font
import "os"
func buildDefault() ([]byte, error) {
// Try Noto first, but fall back to Droid as the latter was deprecated
noto, nerr := os.ReadFile("/usr/share/fonts/truetype/noto/NotoSans-Regular.ttf")
if nerr != nil {
if droid, err := os.ReadFile("/usr/share/fonts/truetype/droid/DroidSans.ttf"); err == nil {
return droid, nil
}
}
return noto, nerr
}
func buildMonospace() ([]byte, error) {
// Try Noto first, but fall back to Droid as the latter was deprecated
noto, nerr := os.ReadFile("/usr/share/fonts/truetype/noto/NotoMono-Regular.ttf")
if nerr != nil {
if droid, err := os.ReadFile("/usr/share/fonts/truetype/droid/DroidSansMono.ttf"); err == nil {
return droid, nil
}
}
return noto, nerr
}

57
exp/font/font_test.go Normal file
View file

@ -0,0 +1,57 @@
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build linux || darwin
package font
import (
"bytes"
"fmt"
"testing"
)
func looksLikeATTF(b []byte) error {
if len(b) < 256 {
return fmt.Errorf("not a TTF: not enough data")
}
b = b[:256]
// Look for the 4-byte magic header. See
// http://www.microsoft.com/typography/otspec/otff.htm
switch string(b[:4]) {
case "\x00\x01\x00\x00", "ttcf":
// No-op.
default:
return fmt.Errorf("not a TTF: missing magic header")
}
// Look for a glyf table.
if i := bytes.Index(b, []byte("glyf")); i < 0 {
return fmt.Errorf(`not a TTF: missing "glyf" table`)
} else if i%0x10 != 0x0c {
return fmt.Errorf(`not a TTF: invalid "glyf" offset 0x%02x`, i)
}
return nil
}
func TestLoadDefault(t *testing.T) {
def, err := buildDefault()
if err != nil {
t.Skip("default font not found")
}
if err := looksLikeATTF(def); err != nil {
t.Errorf("default font: %v", err)
}
}
func TestLoadMonospace(t *testing.T) {
mono, err := buildMonospace()
if err != nil {
t.Skip("mono font not found")
}
if err := looksLikeATTF(mono); err != nil {
t.Errorf("monospace font: %v", err)
}
}

View file

@ -0,0 +1,94 @@
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build darwin && !ios
package glutil
// TODO(crawshaw): Only used in glutil tests for now (cgo is not support in _test.go files).
// TODO(crawshaw): Export some kind of Context. Work out what we can offer, where. Maybe just for tests.
// TODO(crawshaw): Support android and windows.
/*
#cgo CFLAGS: -DGL_SILENCE_DEPRECATION
#cgo LDFLAGS: -framework OpenGL
#import <OpenGL/OpenGL.h>
#import <OpenGL/gl3.h>
CGLError CGCreate(CGLContextObj* ctx) {
CGLError err;
CGLPixelFormatAttribute attributes[] = {
kCGLPFAOpenGLProfile, (CGLPixelFormatAttribute)kCGLOGLPVersion_3_2_Core,
kCGLPFAColorSize, (CGLPixelFormatAttribute)24,
kCGLPFAAlphaSize, (CGLPixelFormatAttribute)8,
kCGLPFADepthSize, (CGLPixelFormatAttribute)16,
kCGLPFAAccelerated,
kCGLPFADoubleBuffer,
(CGLPixelFormatAttribute) 0
};
CGLPixelFormatObj pix;
GLint num;
if ((err = CGLChoosePixelFormat(attributes, &pix, &num)) != kCGLNoError) {
return err;
}
if ((err = CGLCreateContext(pix, 0, ctx)) != kCGLNoError) {
return err;
}
if ((err = CGLDestroyPixelFormat(pix)) != kCGLNoError) {
return err;
}
if ((err = CGLSetCurrentContext(*ctx)) != kCGLNoError) {
return err;
}
if ((err = CGLLockContext(*ctx)) != kCGLNoError) {
return err;
}
return kCGLNoError;
}
*/
import "C"
import (
"fmt"
"runtime"
)
// contextGL holds a copy of the OpenGL Context from thread-local storage.
//
// Do not move a contextGL between goroutines or OS threads.
type contextGL struct {
ctx C.CGLContextObj
}
// createContext creates an OpenGL context, binds it as the current context
// stored in thread-local storage, and locks the current goroutine to an os
// thread.
func createContext() (*contextGL, error) {
// The OpenGL active context is stored in TLS.
runtime.LockOSThread()
c := new(contextGL)
if cglErr := C.CGCreate(&c.ctx); cglErr != C.kCGLNoError {
return nil, fmt.Errorf("CGL: %v", C.GoString(C.CGLErrorString(cglErr)))
}
// Using attribute arrays in OpenGL 3.3 requires the use of a VBA.
// But VBAs don't exist in ES 2. So we bind a default one.
var id C.GLuint
C.glGenVertexArrays(1, &id)
C.glBindVertexArray(id)
return c, nil
}
// destroy destroys an OpenGL context and unlocks the current goroutine from
// its os thread.
func (c *contextGL) destroy() {
C.CGLUnlockContext(c.ctx)
C.CGLSetCurrentContext(nil)
C.CGLDestroyContext(c.ctx)
c.ctx = nil
runtime.UnlockOSThread()
}

View file

@ -0,0 +1,105 @@
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build linux && !android
package glutil
/*
#cgo LDFLAGS: -lEGL
#include <EGL/egl.h>
#include <stdio.h>
#include <stdlib.h>
void createContext(EGLDisplay *out_dpy, EGLContext *out_ctx, EGLSurface *out_surf) {
EGLDisplay e_dpy = eglGetDisplay(EGL_DEFAULT_DISPLAY);
if (!e_dpy) {
fprintf(stderr, "eglGetDisplay failed\n");
exit(1);
}
EGLint e_major, e_minor;
if (!eglInitialize(e_dpy, &e_major, &e_minor)) {
fprintf(stderr, "eglInitialize failed\n");
exit(1);
}
eglBindAPI(EGL_OPENGL_ES_API);
static const EGLint config_attribs[] = {
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
EGL_SURFACE_TYPE, EGL_PBUFFER_BIT,
EGL_BLUE_SIZE, 8,
EGL_GREEN_SIZE, 8,
EGL_RED_SIZE, 8,
EGL_CONFIG_CAVEAT, EGL_NONE,
EGL_NONE
};
EGLConfig config;
EGLint num_configs;
if (!eglChooseConfig(e_dpy, config_attribs, &config, 1, &num_configs)) {
fprintf(stderr, "eglChooseConfig failed\n");
exit(1);
}
static const EGLint ctx_attribs[] = {
EGL_CONTEXT_CLIENT_VERSION, 2,
EGL_NONE
};
EGLContext e_ctx = eglCreateContext(e_dpy, config, EGL_NO_CONTEXT, ctx_attribs);
if (e_ctx == EGL_NO_CONTEXT) {
fprintf(stderr, "eglCreateContext failed\n");
exit(1);
}
static const EGLint pbuf_attribs[] = {
EGL_NONE
};
EGLSurface e_surf = eglCreatePbufferSurface(e_dpy, config, pbuf_attribs);
if (e_surf == EGL_NO_SURFACE) {
fprintf(stderr, "eglCreatePbufferSurface failed\n");
exit(1);
}
if (!eglMakeCurrent(e_dpy, e_surf, e_surf, e_ctx)) {
fprintf(stderr, "eglMakeCurrent failed\n");
exit(1);
}
*out_surf = e_surf;
*out_ctx = e_ctx;
*out_dpy = e_dpy;
}
void destroyContext(EGLDisplay e_dpy, EGLContext e_ctx, EGLSurface e_surf) {
if (!eglMakeCurrent(e_dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)) {
fprintf(stderr, "eglMakeCurrent failed\n");
exit(1);
}
if (!eglDestroySurface(e_dpy, e_surf)) {
fprintf(stderr, "eglDestroySurface failed\n");
exit(1);
}
if (!eglDestroyContext(e_dpy, e_ctx)) {
fprintf(stderr, "eglDestroyContext failed\n");
exit(1);
}
}
*/
import "C"
import (
"runtime"
)
type contextGL struct {
dpy C.EGLDisplay
ctx C.EGLContext
surf C.EGLSurface
}
func createContext() (*contextGL, error) {
runtime.LockOSThread()
c := &contextGL{}
C.createContext(&c.dpy, &c.ctx, &c.surf)
return c, nil
}
func (c *contextGL) destroy() {
C.destroyContext(c.dpy, c.ctx, c.surf)
runtime.UnlockOSThread()
}

6
exp/gl/glutil/doc.go Normal file
View file

@ -0,0 +1,6 @@
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package glutil implements OpenGL utility functions.
package glutil

333
exp/gl/glutil/glimage.go Normal file
View file

@ -0,0 +1,333 @@
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build linux || darwin || windows
package glutil
import (
"encoding/binary"
"image"
"runtime"
"sync"
"golang.org/x/mobile/event/size"
"golang.org/x/mobile/exp/f32"
"golang.org/x/mobile/geom"
"golang.org/x/mobile/gl"
)
// Images maintains the shared state used by a set of *Image objects.
type Images struct {
glctx gl.Context
quadXY gl.Buffer
quadUV gl.Buffer
program gl.Program
pos gl.Attrib
mvp gl.Uniform
uvp gl.Uniform
inUV gl.Attrib
textureSample gl.Uniform
mu sync.Mutex
activeImages int
}
// NewImages creates an *Images.
func NewImages(glctx gl.Context) *Images {
program, err := CreateProgram(glctx, vertexShader, fragmentShader)
if err != nil {
panic(err)
}
p := &Images{
glctx: glctx,
quadXY: glctx.CreateBuffer(),
quadUV: glctx.CreateBuffer(),
program: program,
pos: glctx.GetAttribLocation(program, "pos"),
mvp: glctx.GetUniformLocation(program, "mvp"),
uvp: glctx.GetUniformLocation(program, "uvp"),
inUV: glctx.GetAttribLocation(program, "inUV"),
textureSample: glctx.GetUniformLocation(program, "textureSample"),
}
glctx.BindBuffer(gl.ARRAY_BUFFER, p.quadXY)
glctx.BufferData(gl.ARRAY_BUFFER, quadXYCoords, gl.STATIC_DRAW)
glctx.BindBuffer(gl.ARRAY_BUFFER, p.quadUV)
glctx.BufferData(gl.ARRAY_BUFFER, quadUVCoords, gl.STATIC_DRAW)
return p
}
// Release releases any held OpenGL resources.
// All *Image objects must be released first, or this function panics.
func (p *Images) Release() {
if p.program == (gl.Program{}) {
return
}
p.mu.Lock()
rem := p.activeImages
p.mu.Unlock()
if rem > 0 {
panic("glutil.Images.Release called, but active *Image objects remain")
}
p.glctx.DeleteProgram(p.program)
p.glctx.DeleteBuffer(p.quadXY)
p.glctx.DeleteBuffer(p.quadUV)
p.program = gl.Program{}
}
// Image bridges between an *image.RGBA and an OpenGL texture.
//
// The contents of the *image.RGBA can be uploaded as a texture and drawn as a
// 2D quad.
//
// The number of active Images must fit in the system's OpenGL texture limit.
// The typical use of an Image is as a texture atlas.
type Image struct {
RGBA *image.RGBA
gltex gl.Texture
width int
height int
images *Images
}
// NewImage creates an Image of the given size.
//
// Both a host-memory *image.RGBA and a GL texture are created.
func (p *Images) NewImage(w, h int) *Image {
dx := roundToPower2(w)
dy := roundToPower2(h)
// TODO(crawshaw): Using VertexAttribPointer we can pass texture
// data with a stride, which would let us use the exact number of
// pixels on the host instead of the rounded up power 2 size.
m := image.NewRGBA(image.Rect(0, 0, dx, dy))
img := &Image{
RGBA: m.SubImage(image.Rect(0, 0, w, h)).(*image.RGBA),
images: p,
width: dx,
height: dy,
}
p.mu.Lock()
p.activeImages++
p.mu.Unlock()
img.gltex = p.glctx.CreateTexture()
p.glctx.BindTexture(gl.TEXTURE_2D, img.gltex)
p.glctx.TexImage2D(gl.TEXTURE_2D, 0, gl.RGBA, img.width, img.height, gl.RGBA, gl.UNSIGNED_BYTE, nil)
p.glctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
p.glctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR)
p.glctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)
p.glctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE)
runtime.SetFinalizer(img, (*Image).Release)
return img
}
func roundToPower2(x int) int {
x2 := 1
for x2 < x {
x2 *= 2
}
return x2
}
// Upload copies the host image data to the GL device.
func (img *Image) Upload() {
img.images.glctx.BindTexture(gl.TEXTURE_2D, img.gltex)
img.images.glctx.TexSubImage2D(gl.TEXTURE_2D, 0, 0, 0, img.width, img.height, gl.RGBA, gl.UNSIGNED_BYTE, img.RGBA.Pix)
}
// Release invalidates the Image and removes any underlying data structures.
// The Image cannot be used after being deleted.
func (img *Image) Release() {
if img.gltex == (gl.Texture{}) {
return
}
img.images.glctx.DeleteTexture(img.gltex)
img.gltex = gl.Texture{}
img.images.mu.Lock()
img.images.activeImages--
img.images.mu.Unlock()
}
// Draw draws the srcBounds part of the image onto a parallelogram, defined by
// three of its corners, in the current GL framebuffer.
func (img *Image) Draw(sz size.Event, topLeft, topRight, bottomLeft geom.Point, srcBounds image.Rectangle) {
glimage := img.images
glctx := img.images.glctx
glctx.BlendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA)
glctx.Enable(gl.BLEND)
// TODO(crawshaw): Adjust viewport for the top bar on android?
glctx.UseProgram(glimage.program)
{
// We are drawing a parallelogram PQRS, defined by three of its
// corners, onto the entire GL framebuffer ABCD. The two quads may
// actually be equal, but in the general case, PQRS can be smaller,
// and PQRS is not necessarily axis-aligned.
//
// A +---------------+ B
// | P +-----+ Q |
// | | | |
// | S +-----+ R |
// D +---------------+ C
//
// There are two co-ordinate spaces: geom space and framebuffer space.
// In geom space, the ABCD rectangle is:
//
// (0, 0) (geom.Width, 0)
// (0, geom.Height) (geom.Width, geom.Height)
//
// and the PQRS quad is:
//
// (topLeft.X, topLeft.Y) (topRight.X, topRight.Y)
// (bottomLeft.X, bottomLeft.Y) (implicit, implicit)
//
// In framebuffer space, the ABCD rectangle is:
//
// (-1, +1) (+1, +1)
// (-1, -1) (+1, -1)
//
// First of all, convert from geom space to framebuffer space. For
// later convenience, we divide everything by 2 here: px2 is half of
// the P.X co-ordinate (in framebuffer space).
px2 := -0.5 + float32(topLeft.X/sz.WidthPt)
py2 := +0.5 - float32(topLeft.Y/sz.HeightPt)
qx2 := -0.5 + float32(topRight.X/sz.WidthPt)
qy2 := +0.5 - float32(topRight.Y/sz.HeightPt)
sx2 := -0.5 + float32(bottomLeft.X/sz.WidthPt)
sy2 := +0.5 - float32(bottomLeft.Y/sz.HeightPt)
// Next, solve for the affine transformation matrix
// [ a00 a01 a02 ]
// a = [ a10 a11 a12 ]
// [ 0 0 1 ]
// that maps A to P:
// a × [ -1 +1 1 ]' = [ 2*px2 2*py2 1 ]'
// and likewise maps B to Q and D to S. Solving those three constraints
// implies that C maps to R, since affine transformations keep parallel
// lines parallel. This gives 6 equations in 6 unknowns:
// -a00 + a01 + a02 = 2*px2
// -a10 + a11 + a12 = 2*py2
// +a00 + a01 + a02 = 2*qx2
// +a10 + a11 + a12 = 2*qy2
// -a00 - a01 + a02 = 2*sx2
// -a10 - a11 + a12 = 2*sy2
// which gives:
// a00 = (2*qx2 - 2*px2) / 2 = qx2 - px2
// and similarly for the other elements of a.
writeAffine(glctx, glimage.mvp, &f32.Affine{{
qx2 - px2,
px2 - sx2,
qx2 + sx2,
}, {
qy2 - py2,
py2 - sy2,
qy2 + sy2,
}})
}
{
// Mapping texture co-ordinates is similar, except that in texture
// space, the ABCD rectangle is:
//
// (0,0) (1,0)
// (0,1) (1,1)
//
// and the PQRS quad is always axis-aligned. First of all, convert
// from pixel space to texture space.
w := float32(img.width)
h := float32(img.height)
px := float32(srcBounds.Min.X-img.RGBA.Rect.Min.X) / w
py := float32(srcBounds.Min.Y-img.RGBA.Rect.Min.Y) / h
qx := float32(srcBounds.Max.X-img.RGBA.Rect.Min.X) / w
sy := float32(srcBounds.Max.Y-img.RGBA.Rect.Min.Y) / h
// Due to axis alignment, qy = py and sx = px.
//
// The simultaneous equations are:
// 0 + 0 + a02 = px
// 0 + 0 + a12 = py
// a00 + 0 + a02 = qx
// a10 + 0 + a12 = qy = py
// 0 + a01 + a02 = sx = px
// 0 + a11 + a12 = sy
writeAffine(glctx, glimage.uvp, &f32.Affine{{
qx - px,
0,
px,
}, {
0,
sy - py,
py,
}})
}
glctx.ActiveTexture(gl.TEXTURE0)
glctx.BindTexture(gl.TEXTURE_2D, img.gltex)
glctx.Uniform1i(glimage.textureSample, 0)
glctx.BindBuffer(gl.ARRAY_BUFFER, glimage.quadXY)
glctx.EnableVertexAttribArray(glimage.pos)
glctx.VertexAttribPointer(glimage.pos, 2, gl.FLOAT, false, 0, 0)
glctx.BindBuffer(gl.ARRAY_BUFFER, glimage.quadUV)
glctx.EnableVertexAttribArray(glimage.inUV)
glctx.VertexAttribPointer(glimage.inUV, 2, gl.FLOAT, false, 0, 0)
glctx.DrawArrays(gl.TRIANGLE_STRIP, 0, 4)
glctx.DisableVertexAttribArray(glimage.pos)
glctx.DisableVertexAttribArray(glimage.inUV)
glctx.Disable(gl.BLEND)
}
var quadXYCoords = f32.Bytes(binary.LittleEndian,
-1, +1, // top left
+1, +1, // top right
-1, -1, // bottom left
+1, -1, // bottom right
)
var quadUVCoords = f32.Bytes(binary.LittleEndian,
0, 0, // top left
1, 0, // top right
0, 1, // bottom left
1, 1, // bottom right
)
const vertexShader = `#version 100
uniform mat3 mvp;
uniform mat3 uvp;
attribute vec3 pos;
attribute vec2 inUV;
varying vec2 UV;
void main() {
vec3 p = pos;
p.z = 1.0;
gl_Position = vec4(mvp * p, 1);
UV = (uvp * vec3(inUV, 1)).xy;
}
`
const fragmentShader = `#version 100
precision mediump float;
varying vec2 UV;
uniform sampler2D textureSample;
void main(){
gl_FragColor = texture2D(textureSample, UV);
}
`

View file

@ -0,0 +1,212 @@
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build darwin || (linux && !android)
// TODO(crawshaw): Run tests on other OSs when more contexts are supported.
package glutil
import (
"image"
"image/color"
)
// TODO: Re-enable test.
/*func TestImage(t *testing.T) {
done := make(chan error)
defer close(done)
go func() {
runtime.LockOSThread()
ctx, err := createContext()
done <- err
for {
select {
case <-gl.WorkAvailable:
gl.DoWork()
case <-done:
ctx.destroy()
return
}
}
}()
if err := <-done; err != nil {
t.Fatalf("cannot create GL context: %v", err)
}
start()
defer stop()
// GL testing strategy:
// 1. Create an offscreen framebuffer object.
// 2. Configure framebuffer to render to a GL texture.
// 3. Run test code: use glimage to draw testdata.
// 4. Copy GL texture back into system memory.
// 5. Compare to a pre-computed image.
f, err := os.Open("../../../testdata/testpattern.png")
if err != nil {
t.Fatal(err)
}
defer f.Close()
src, _, err := image.Decode(f)
if err != nil {
t.Fatal(err)
}
const (
pixW = 100
pixH = 100
ptW = geom.Pt(50)
ptH = geom.Pt(50)
)
sz := size.Event{
WidthPx: pixW,
HeightPx: pixH,
WidthPt: ptW,
HeightPt: ptH,
PixelsPerPt: float32(pixW) / float32(ptW),
}
fBuf := gl.CreateFramebuffer()
gl.BindFramebuffer(gl.FRAMEBUFFER, fBuf)
colorBuf := gl.CreateRenderbuffer()
gl.BindRenderbuffer(gl.RENDERBUFFER, colorBuf)
// https://www.khronos.org/opengles/sdk/docs/man/xhtml/glRenderbufferStorage.xml
// says that the internalFormat "must be one of the following symbolic constants:
// GL_RGBA4, GL_RGB565, GL_RGB5_A1, GL_DEPTH_COMPONENT16, or GL_STENCIL_INDEX8".
gl.RenderbufferStorage(gl.RENDERBUFFER, gl.RGB565, pixW, pixH)
gl.FramebufferRenderbuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.RENDERBUFFER, colorBuf)
if status := gl.CheckFramebufferStatus(gl.FRAMEBUFFER); status != gl.FRAMEBUFFER_COMPLETE {
t.Fatalf("framebuffer create failed: %v", status)
}
allocs := testing.AllocsPerRun(100, func() {
gl.ClearColor(0, 0, 1, 1) // blue
})
if allocs != 0 {
t.Errorf("unexpected allocations from calling gl.ClearColor: %f", allocs)
}
gl.Clear(gl.COLOR_BUFFER_BIT)
gl.Viewport(0, 0, pixW, pixH)
m := NewImage(src.Bounds().Dx(), src.Bounds().Dy())
b := m.RGBA.Bounds()
draw.Draw(m.RGBA, b, src, src.Bounds().Min, draw.Src)
m.Upload()
b.Min.X += 10
b.Max.Y /= 2
// All-integer right-angled triangles offsetting the
// box: 24-32-40, 12-16-20.
ptTopLeft := geom.Point{0, 24}
ptTopRight := geom.Point{32, 0}
ptBottomLeft := geom.Point{12, 24 + 16}
ptBottomRight := geom.Point{12 + 32, 16}
m.Draw(sz, ptTopLeft, ptTopRight, ptBottomLeft, b)
// For unknown reasons, a windowless OpenGL context renders upside-
// down. That is, a quad covering the initial viewport spans:
//
// (-1, -1) ( 1, -1)
// (-1, 1) ( 1, 1)
//
// To avoid modifying live code for tests, we flip the rows
// recovered from the renderbuffer. We are not the first:
//
// http://lists.apple.com/archives/mac-opengl/2010/Jun/msg00080.html
got := image.NewRGBA(image.Rect(0, 0, pixW, pixH))
upsideDownPix := make([]byte, len(got.Pix))
gl.ReadPixels(upsideDownPix, 0, 0, pixW, pixH, gl.RGBA, gl.UNSIGNED_BYTE)
for y := 0; y < pixH; y++ {
i0 := (pixH - 1 - y) * got.Stride
i1 := i0 + pixW*4
copy(got.Pix[y*got.Stride:], upsideDownPix[i0:i1])
}
drawCross(got, 0, 0)
drawCross(got, int(ptTopLeft.X.Px(sz.PixelsPerPt)), int(ptTopLeft.Y.Px(sz.PixelsPerPt)))
drawCross(got, int(ptBottomRight.X.Px(sz.PixelsPerPt)), int(ptBottomRight.Y.Px(sz.PixelsPerPt)))
drawCross(got, pixW-1, pixH-1)
const wantPath = "../../../testdata/testpattern-window.png"
f, err = os.Open(wantPath)
if err != nil {
t.Fatal(err)
}
defer f.Close()
wantSrc, _, err := image.Decode(f)
if err != nil {
t.Fatal(err)
}
want, ok := wantSrc.(*image.RGBA)
if !ok {
b := wantSrc.Bounds()
want = image.NewRGBA(b)
draw.Draw(want, b, wantSrc, b.Min, draw.Src)
}
if !imageEq(got, want) {
// Write out the image we got.
f, err = ioutil.TempFile("", "testpattern-window-got")
if err != nil {
t.Fatal(err)
}
f.Close()
gotPath := f.Name() + ".png"
f, err = os.Create(gotPath)
if err != nil {
t.Fatal(err)
}
if err := png.Encode(f, got); err != nil {
t.Fatal(err)
}
if err := f.Close(); err != nil {
t.Fatal(err)
}
t.Errorf("got\n%s\nwant\n%s", gotPath, wantPath)
}
}*/
func drawCross(m *image.RGBA, x, y int) {
c := color.RGBA{0xff, 0, 0, 0xff} // red
m.SetRGBA(x+0, y-2, c)
m.SetRGBA(x+0, y-1, c)
m.SetRGBA(x-2, y+0, c)
m.SetRGBA(x-1, y+0, c)
m.SetRGBA(x+0, y+0, c)
m.SetRGBA(x+1, y+0, c)
m.SetRGBA(x+2, y+0, c)
m.SetRGBA(x+0, y+1, c)
m.SetRGBA(x+0, y+2, c)
}
func eqEpsilon(x, y uint8) bool {
const epsilon = 9
return x-y < epsilon || y-x < epsilon
}
func colorEq(c0, c1 color.RGBA) bool {
return eqEpsilon(c0.R, c1.R) && eqEpsilon(c0.G, c1.G) && eqEpsilon(c0.B, c1.B) && eqEpsilon(c0.A, c1.A)
}
func imageEq(m0, m1 *image.RGBA) bool {
b0 := m0.Bounds()
b1 := m1.Bounds()
if b0 != b1 {
return false
}
badPx := 0
for y := b0.Min.Y; y < b0.Max.Y; y++ {
for x := b0.Min.X; x < b0.Max.X; x++ {
c0, c1 := m0.At(x, y).(color.RGBA), m1.At(x, y).(color.RGBA)
if !colorEq(c0, c1) {
badPx++
}
}
}
badFrac := float64(badPx) / float64(b0.Dx()*b0.Dy())
return badFrac < 0.01
}

75
exp/gl/glutil/glutil.go Normal file
View file

@ -0,0 +1,75 @@
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build darwin || linux || windows
package glutil
import (
"fmt"
"golang.org/x/mobile/exp/f32"
"golang.org/x/mobile/gl"
)
// CreateProgram creates, compiles, and links a gl.Program.
func CreateProgram(glctx gl.Context, vertexSrc, fragmentSrc string) (gl.Program, error) {
program := glctx.CreateProgram()
if program.Value == 0 {
return gl.Program{}, fmt.Errorf("glutil: no programs available")
}
vertexShader, err := loadShader(glctx, gl.VERTEX_SHADER, vertexSrc)
if err != nil {
return gl.Program{}, err
}
fragmentShader, err := loadShader(glctx, gl.FRAGMENT_SHADER, fragmentSrc)
if err != nil {
glctx.DeleteShader(vertexShader)
return gl.Program{}, err
}
glctx.AttachShader(program, vertexShader)
glctx.AttachShader(program, fragmentShader)
glctx.LinkProgram(program)
// Flag shaders for deletion when program is unlinked.
glctx.DeleteShader(vertexShader)
glctx.DeleteShader(fragmentShader)
if glctx.GetProgrami(program, gl.LINK_STATUS) == 0 {
defer glctx.DeleteProgram(program)
return gl.Program{}, fmt.Errorf("glutil: %s", glctx.GetProgramInfoLog(program))
}
return program, nil
}
func loadShader(glctx gl.Context, shaderType gl.Enum, src string) (gl.Shader, error) {
shader := glctx.CreateShader(shaderType)
if shader.Value == 0 {
return gl.Shader{}, fmt.Errorf("glutil: could not create shader (type %v)", shaderType)
}
glctx.ShaderSource(shader, src)
glctx.CompileShader(shader)
if glctx.GetShaderi(shader, gl.COMPILE_STATUS) == 0 {
defer glctx.DeleteShader(shader)
return gl.Shader{}, fmt.Errorf("shader compile: %s", glctx.GetShaderInfoLog(shader))
}
return shader, nil
}
// writeAffine writes the contents of an Affine to a 3x3 matrix GL uniform.
func writeAffine(glctx gl.Context, u gl.Uniform, a *f32.Affine) {
var m [9]float32
m[0*3+0] = a[0][0]
m[0*3+1] = a[1][0]
m[0*3+2] = 0
m[1*3+0] = a[0][1]
m[1*3+1] = a[1][1]
m[1*3+2] = 0
m[2*3+0] = a[0][2]
m[2*3+1] = a[1][2]
m[2*3+2] = 1
glctx.UniformMatrix3fv(u, m[:])
}

85
exp/sensor/android.c Normal file
View file

@ -0,0 +1,85 @@
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build android
// +build android
#include <stdlib.h>
#include <jni.h>
#include <android/sensor.h>
#define GO_ANDROID_SENSOR_LOOPER_ID 100
#define GO_ANDROID_READ_TIMEOUT_MS 1000
ASensorEventQueue* queue = NULL;
ALooper* looper = NULL;
static ASensorManager* getSensorManager() {
#pragma clang diagnostic push
// Builders convert C warnings to errors, so suppress the
// error from ASensorManager_getInstance being deprecated
// in Android 26.
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
return ASensorManager_getInstance();
#pragma clang diagnostic pop
}
void GoAndroid_createManager() {
ASensorManager* manager = getSensorManager();
looper = ALooper_forThread();
if (looper == NULL) {
looper = ALooper_prepare(ALOOPER_PREPARE_ALLOW_NON_CALLBACKS);
}
queue = ASensorManager_createEventQueue(manager, looper, GO_ANDROID_SENSOR_LOOPER_ID, NULL, NULL);
}
int GoAndroid_enableSensor(int s, int32_t usec) {
ASensorManager* manager = getSensorManager();
const ASensor* sensor = ASensorManager_getDefaultSensor(manager, s);
if (sensor == NULL) {
return 1;
}
ASensorEventQueue_enableSensor(queue, sensor);
ASensorEventQueue_setEventRate(queue, sensor, usec);
return 0;
}
void GoAndroid_disableSensor(int s) {
ASensorManager* manager = getSensorManager();
const ASensor* sensor = ASensorManager_getDefaultSensor(manager, s);
ASensorEventQueue_disableSensor(queue, sensor);
}
int GoAndroid_readQueue(int n, int32_t* types, int64_t* timestamps, float* vectors) {
int id;
int events;
ASensorEvent event;
int i = 0;
// Try n times read from the event queue.
// If anytime timeout occurs, don't retry to read and immediately return.
// Consume the event queue entirely between polls.
while (i < n && (id = ALooper_pollOnce(GO_ANDROID_READ_TIMEOUT_MS, NULL, &events, NULL)) >= 0) {
if (id != GO_ANDROID_SENSOR_LOOPER_ID) {
continue;
}
while (i < n && ASensorEventQueue_getEvents(queue, &event, 1)) {
types[i] = event.type;
timestamps[i] = event.timestamp;
vectors[i*3] = event.vector.x;
vectors[i*3+1] = event.vector.y;
vectors[i*3+2] = event.vector.z;
i++;
}
}
return i;
}
void GoAndroid_destroyManager() {
ASensorManager* manager = getSensorManager();
ASensorManager_destroyEventQueue(manager, queue);
queue = NULL;
looper = NULL;
}

206
exp/sensor/android.go Normal file
View file

@ -0,0 +1,206 @@
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build android
package sensor
/*
#cgo LDFLAGS: -landroid
#include <stdlib.h>
#include <android/sensor.h>
void GoAndroid_createManager();
void GoAndroid_destroyManager();
int GoAndroid_enableSensor(int, int32_t);
void GoAndroid_disableSensor(int);
int GoAndroid_readQueue(int n, int32_t* types, int64_t* timestamps, float* vectors);
*/
import "C"
import (
"fmt"
"runtime"
"sync"
"time"
"unsafe"
)
var (
collectingMu sync.Mutex // guards collecting
// collecting is true if sensor event collecting background
// job has already started.
collecting bool
)
// closeSignal destroys the underlying looper and event queue.
type closeSignal struct{}
// readSignal reads up to len(dst) events and mutates n with
// the number of returned events.
type readSignal struct {
dst []Event
n *int
}
// enableSignal enables the sensors events on the underlying
// event queue for the specified sensor type with the specified
// latency criterion.
type enableSignal struct {
t Type
delay time.Duration
err *error
}
// disableSignal disables the events on the underlying event queue
// from the sensor specified.
type disableSignal struct {
t Type
}
type inOut struct {
in interface{}
out chan struct{}
}
var inout = make(chan inOut)
// init inits the manager and creates a goroutine to proxy the CGO calls.
// All actions related to an ALooper needs to be performed from the same
// OS thread. The goroutine proxy locks itself to an OS thread and handles the
// CGO traffic on the same thread.
func init() {
go func() {
runtime.LockOSThread()
C.GoAndroid_createManager()
for {
v := <-inout
switch s := v.in.(type) {
case enableSignal:
usecsDelay := s.delay.Nanoseconds() / 1000
code := int(C.GoAndroid_enableSensor(typeToInt(s.t), C.int32_t(usecsDelay)))
if code != 0 {
*s.err = fmt.Errorf("sensor: no default %v sensor on the device", s.t)
}
case disableSignal:
C.GoAndroid_disableSensor(typeToInt(s.t))
case readSignal:
n := readEvents(s.dst)
*s.n = n
case closeSignal:
C.GoAndroid_destroyManager()
close(v.out)
return // we don't need this goroutine anymore
}
close(v.out)
}
}()
}
// enable enables the sensor t on sender. A non-nil sender is
// required before calling enable.
func enable(t Type, delay time.Duration) error {
startCollecting()
var err error
done := make(chan struct{})
inout <- inOut{
in: enableSignal{t: t, delay: delay, err: &err},
out: done,
}
<-done
return err
}
func startCollecting() {
collectingMu.Lock()
defer collectingMu.Unlock()
if collecting {
// already collecting.
return
}
collecting = true
go func() {
ev := make([]Event, 8)
var n int
for {
done := make(chan struct{})
inout <- inOut{
in: readSignal{dst: ev, n: &n},
out: done,
}
<-done
for i := 0; i < n; i++ {
sender.Send(ev[i])
}
}
}()
}
func disable(t Type) error {
done := make(chan struct{})
inout <- inOut{
in: disableSignal{t: t},
out: done,
}
<-done
return nil
}
func readEvents(e []Event) int {
num := len(e)
types := make([]C.int32_t, num)
timestamps := make([]C.int64_t, num)
vectors := make([]C.float, 3*num)
n := int(C.GoAndroid_readQueue(
C.int(num),
(*C.int32_t)(unsafe.Pointer(&types[0])),
(*C.int64_t)(unsafe.Pointer(&timestamps[0])),
(*C.float)(unsafe.Pointer(&vectors[0]))),
)
for i := 0; i < n; i++ {
e[i] = Event{
Sensor: intToType[int(types[i])],
Timestamp: int64(timestamps[i]),
Data: []float64{
float64(vectors[i*3]),
float64(vectors[i*3+1]),
float64(vectors[i*3+2]),
},
}
}
return n
}
// TODO(jbd): Remove destroy?
func destroy() error {
done := make(chan struct{})
inout <- inOut{
in: closeSignal{},
out: done,
}
<-done
return nil
}
var intToType = map[int]Type{
C.ASENSOR_TYPE_ACCELEROMETER: Accelerometer,
C.ASENSOR_TYPE_GYROSCOPE: Gyroscope,
C.ASENSOR_TYPE_MAGNETIC_FIELD: Magnetometer,
}
func typeToInt(t Type) C.int {
for k, v := range intToType {
if v == t {
return C.int(k)
}
}
return C.int(-1)
}

153
exp/sensor/darwin_armx.go Normal file
View file

@ -0,0 +1,153 @@
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build darwin && (arm || arm64)
package sensor
/*
#cgo CFLAGS: -x objective-c
#cgo LDFLAGS: -framework CoreMotion
#import <stdlib.h>
void GoIOS_createManager();
void GoIOS_startAccelerometer(float interval);
void GoIOS_stopAccelerometer();
void GoIOS_readAccelerometer(int64_t* timestamp, float* vector);
void GoIOS_startGyro(float interval);
void GoIOS_stopGyro();
void GoIOS_readGyro(int64_t* timestamp, float* vector);
void GoIOS_startMagneto(float interval);
void GoIOS_stopMagneto();
void GoIOS_readMagneto(int64_t* timestamp, float* vector);
void GoIOS_destroyManager();
*/
import "C"
import (
"fmt"
"sync"
"time"
"unsafe"
)
var channels struct {
sync.Mutex
done [nTypes]chan struct{}
}
func init() {
C.GoIOS_createManager()
}
// minDelay is the minimum delay allowed.
//
// From Event Handling Guide for iOS:
//
// "You can set the reporting interval to be as small as 10
// milliseconds (ms), which corresponds to a 100 Hz update rate,
// but most app operate sufficiently with a larger interval."
//
// There is no need to poll more frequently than once every 10ms.
//
// https://developer.apple.com/library/ios/documentation/EventHandling/Conceptual/EventHandlingiPhoneOS/motion_event_basics/motion_event_basics.html
const minDelay = 10 * time.Millisecond
// enable enables the sensor t on sender. A non-nil sender is
// required before calling enable.
func enable(t Type, delay time.Duration) error {
channels.Lock()
defer channels.Unlock()
if channels.done[t] != nil {
return fmt.Errorf("sensor: cannot enable; %v sensor is already enabled", t)
}
channels.done[t] = make(chan struct{})
if delay < minDelay {
delay = minDelay
}
interval := C.float(float64(delay) / float64(time.Second))
switch t {
case Accelerometer:
C.GoIOS_startAccelerometer(interval)
case Gyroscope:
C.GoIOS_startGyro(interval)
case Magnetometer:
C.GoIOS_startMagneto(interval)
}
go pollSensor(t, delay, channels.done[t])
return nil
}
func disable(t Type) error {
channels.Lock()
defer channels.Unlock()
if channels.done[t] == nil {
return fmt.Errorf("sensor: cannot disable; %v sensor is not enabled", t)
}
close(channels.done[t])
channels.done[t] = nil
switch t {
case Accelerometer:
C.GoIOS_stopAccelerometer()
case Gyroscope:
C.GoIOS_stopGyro()
case Magnetometer:
C.GoIOS_stopMagneto()
}
return nil
}
func pollSensor(t Type, d time.Duration, done chan struct{}) {
var lastTimestamp int64
var timestamp C.int64_t
var ev [3]C.float
for {
select {
case <-done:
return
default:
tp := (*C.int64_t)(unsafe.Pointer(&timestamp))
vp := (*C.float)(unsafe.Pointer(&ev[0]))
switch t {
case Accelerometer:
C.GoIOS_readAccelerometer(tp, vp)
case Gyroscope:
C.GoIOS_readGyro(tp, vp)
case Magnetometer:
C.GoIOS_readMagneto(tp, vp)
}
ts := int64(timestamp)
if ts > lastTimestamp {
// TODO(jbd): Do we need to convert the values to another unit?
// How does iOS units compare to the Android units.
sender.Send(Event{
Sensor: t,
Timestamp: ts,
Data: []float64{float64(ev[0]), float64(ev[1]), float64(ev[2])},
})
lastTimestamp = ts
time.Sleep(d / 2)
}
}
}
}
// TODO(jbd): Remove destroy?
func destroy() error {
C.GoIOS_destroyManager()
return nil
}

71
exp/sensor/darwin_armx.m Normal file
View file

@ -0,0 +1,71 @@
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build darwin && (arm || arm64)
// +build darwin
// +build arm arm64
#import <CoreMotion/CoreMotion.h>
CMMotionManager* manager = nil;
void GoIOS_createManager() {
manager = [[CMMotionManager alloc] init];
}
void GoIOS_startAccelerometer(float interval) {
manager.accelerometerUpdateInterval = interval;
[manager startAccelerometerUpdates];
}
void GoIOS_stopAccelerometer() {
[manager stopAccelerometerUpdates];
}
void GoIOS_readAccelerometer(int64_t* timestamp, float* v) {
CMAccelerometerData* data = manager.accelerometerData;
*timestamp = (int64_t)(data.timestamp * 1000 * 1000);
v[0] = data.acceleration.x;
v[1] = data.acceleration.y;
v[2] = data.acceleration.z;
}
void GoIOS_startGyro(float interval) {
manager.gyroUpdateInterval = interval;
[manager startGyroUpdates];
}
void GoIOS_stopGyro() {
[manager stopGyroUpdates];
}
void GoIOS_readGyro(int64_t* timestamp, float* v) {
CMGyroData* data = manager.gyroData;
*timestamp = (int64_t)(data.timestamp * 1000 * 1000);
v[0] = data.rotationRate.x;
v[1] = data.rotationRate.y;
v[2] = data.rotationRate.z;
}
void GoIOS_startMagneto(float interval) {
manager.magnetometerUpdateInterval = interval;
[manager startMagnetometerUpdates];
}
void GoIOS_stopMagneto() {
[manager stopMagnetometerUpdates];
}
void GoIOS_readMagneto(int64_t* timestamp, float* v) {
CMMagnetometerData* data = manager.magnetometerData;
*timestamp = (int64_t)(data.timestamp * 1000 * 1000);
v[0] = data.magneticField.x;
v[1] = data.magneticField.y;
v[2] = data.magneticField.z;
}
void GoIOS_destroyManager() {
[manager release];
manager = nil;
}

24
exp/sensor/notmobile.go Normal file
View file

@ -0,0 +1,24 @@
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build (linux && !android) || (darwin && !arm && !arm64) || windows
package sensor
import (
"errors"
"time"
)
func enable(t Type, delay time.Duration) error {
return errors.New("sensor: no sensors available")
}
func disable(t Type) error {
return errors.New("sensor: no sensors available")
}
func destroy() error {
return nil
}

130
exp/sensor/sensor.go Normal file
View file

@ -0,0 +1,130 @@
// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package sensor provides sensor events from various movement sensors.
package sensor
import (
"errors"
"sync"
"time"
)
// Type represents a sensor type.
type Type int
var sensorNames = map[Type]string{
Accelerometer: "Accelerometer",
Gyroscope: "Gyroscope",
Magnetometer: "Magnetometer",
}
// String returns the string representation of the sensor type.
func (t Type) String() string {
if n, ok := sensorNames[t]; ok {
return n
}
return "Unknown sensor"
}
const (
Accelerometer = Type(0)
Gyroscope = Type(1)
Magnetometer = Type(2)
nTypes = Type(3)
)
// Event represents a sensor event.
type Event struct {
// Sensor is the type of the sensor the event is coming from.
Sensor Type
// Timestamp is a device specific event time in nanoseconds.
// Timestamps are not Unix times, they represent a time that is
// only valid for the device's default sensor.
Timestamp int64
// Data is the event data.
//
// If the event source is Accelerometer,
// - Data[0]: acceleration force in x axis in m/s^2
// - Data[1]: acceleration force in y axis in m/s^2
// - Data[2]: acceleration force in z axis in m/s^2
//
// If the event source is Gyroscope,
// - Data[0]: rate of rotation around the x axis in rad/s
// - Data[1]: rate of rotation around the y axis in rad/s
// - Data[2]: rate of rotation around the z axis in rad/s
//
// If the event source is Magnetometer,
// - Data[0]: force of gravity along the x axis in m/s^2
// - Data[1]: force of gravity along the y axis in m/s^2
// - Data[2]: force of gravity along the z axis in m/s^2
//
Data []float64
}
// TODO(jbd): Move Sender interface definition to a top-level package.
var (
// senderMu protects sender.
senderMu sync.Mutex
// sender is notified with the sensor data each time a new event is available.
sender Sender
)
// Sender sends an event.
type Sender interface {
Send(event interface{})
}
// Notify registers a Sender and sensor events will be sent to s.
// A typical example of Sender implementations is app.App.
// Once you call Notify, you are not allowed to call it again.
// You cannot call Notify with a nil Sender.
func Notify(s Sender) {
senderMu.Lock()
defer senderMu.Unlock()
if s == nil {
panic("sensor: cannot set a nil sender")
}
if sender != nil {
panic("sensor: another sender is being notified, cannot set s as the sender")
}
sender = s
}
// Enable enables the specified sensor type with the given delay rate.
// Users must set a non-nil Sender via Notify before enabling a sensor,
// otherwise an error will be returned.
func Enable(t Type, delay time.Duration) error {
if t < 0 || int(t) >= len(sensorNames) {
return errors.New("sensor: unknown sensor type")
}
if err := validSender(); err != nil {
return err
}
return enable(t, delay)
}
// Disable disables to feed the manager with the specified sensor.
// Disable is not safe for concurrent use.
func Disable(t Type) error {
if t < 0 || int(t) >= len(sensorNames) {
return errors.New("sensor: unknown sensor type")
}
return disable(t)
}
func validSender() error {
senderMu.Lock()
defer senderMu.Unlock()
if sender == nil {
return errors.New("sensor: no senders to be notified; cannot enable the sensor")
}
return nil
}

26
exp/sprite/clock/clock.go Normal file
View file

@ -0,0 +1,26 @@
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package clock provides a clock and time functions for a sprite engine.
package clock
// A Time represents an instant in sprite time.
//
// The application using the sprite engine is responsible for
// determining sprite time.
//
// Typically time 0 is when the app is initialized and time is
// quantized at the intended frame rate. For example, an app may
// record wall time when it is initialized
//
// var start = time.Now()
//
// and then compute the current instant in time for 60 FPS:
//
// now := clock.Time(time.Since(start) * 60 / time.Second)
//
// An application can pause or reset sprite time, but it must be aware
// of any stateful sprite.Arranger instances that expect time to
// continue.
type Time int32

83
exp/sprite/clock/tween.go Normal file
View file

@ -0,0 +1,83 @@
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package clock
// Standard tween functions.
//
// Easing means a slowing near the timing boundary, as defined by
// a cubic bezier curve. Exact parameters match the CSS properties.
var (
EaseIn = CubicBezier(0.42, 0, 1, 1)
EaseOut = CubicBezier(0, 0, 0.58, 1)
EaseInOut = CubicBezier(0.42, 0, 0.58, 1)
)
// Linear computes the fraction [0,1] that t lies between [t0,t1].
func Linear(t0, t1, t Time) float32 {
if t >= t1 {
return 1
}
if t <= t0 {
return 0
}
return float32(t-t0) / float32(t1-t0)
}
// CubicBezier generates a tween function determined by a Cubic Bézier curve.
//
// The parameters are cubic control parameters. The curve starts at (0,0)
// going toward (x0,y0), and arrives at (1,1) coming from (x1,y1).
func CubicBezier(x0, y0, x1, y1 float32) func(t0, t1, t Time) float32 {
return func(start, end, now Time) float32 {
// A Cubic-Bezier curve restricted to starting at (0,0) and
// ending at (1,1) is defined as
//
// B(t) = 3*(1-t)^2*t*P0 + 3*(1-t)*t^2*P1 + t^3
//
// with derivative
//
// B'(t) = 3*(1-t)^2*P0 + 6*(1-t)*t*(P1-P0) + 3*t^2*(1-P1)
//
// Given a value x ∈ [0,1], we solve for t using Newton's
// method and solve for y using t.
x := Linear(start, end, now)
// Solve for t using x.
t := x
for i := 0; i < 5; i++ {
t2 := t * t
t3 := t2 * t
d := 1 - t
d2 := d * d
nx := 3*d2*t*x0 + 3*d*t2*x1 + t3
dxdt := 3*d2*x0 + 6*d*t*(x1-x0) + 3*t2*(1-x1)
if dxdt == 0 {
break
}
t -= (nx - x) / dxdt
if t <= 0 || t >= 1 {
break
}
}
if t < 0 {
t = 0
}
if t > 1 {
t = 1
}
// Solve for y using t.
t2 := t * t
t3 := t2 * t
d := 1 - t
d2 := d * d
y := 3*d2*t*y0 + 3*d*t2*y1 + t3
return y
}
}

View file

@ -0,0 +1,53 @@
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package clock
import "testing"
func TestLinear(t *testing.T) {
t0 := Time(0)
t1 := Time(6 * 60)
now := Time(3 * 60)
if c := Linear(t0, t1, now); c != 0.5 {
t.Errorf("c=%.2f, want 0.5", c)
}
}
func TestCubicBezier(t *testing.T) {
t0 := Time(0)
t1 := Time(1e6)
tests := []struct {
x0, y0, x1, y1 float32
x, y float32
}{
{0.00, 0.1, 0.4, 1.00, 0.0, 0.00},
{0.00, 0.1, 0.4, 1.00, 0.1, 0.26},
{0.00, 0.1, 0.4, 1.00, 0.5, 0.79},
{0.00, 0.1, 0.4, 1.00, 0.9, 0.99},
{0.00, 0.1, 0.4, 1.00, 1.0, 1.00},
{0.36, 0.2, 0.3, 0.85, 0.0, 0.0},
{0.36, 0.2, 0.3, 0.85, 0.3059, 0.3952},
{0.36, 0.2, 0.3, 0.85, 0.4493, 0.6408},
{0.36, 0.2, 0.3, 0.85, 0.8116, 0.9410},
{0.00, 0.0, 1.0, 1.00, 0.1, 0.1},
{0.00, 0.0, 1.0, 1.00, 0.5, 0.5},
{0.00, 0.0, 1.0, 1.00, 0.9, 0.9},
{0.42, 0.0, 1.0, 1.00, 0.0, 0.0},
}
for _, test := range tests {
cb := CubicBezier(test.x0, test.y0, test.x1, test.y1)
now := t0 + Time(float32(t1-t0)*test.x)
y := cb(t0, t1, now)
const epsilon = 0.01
diff := y - test.y
if diff < -epsilon || +epsilon < diff {
t.Errorf("CubicBezier(%.2f,%.2f,%.2f,%.2f): for x=%.2f got y=%.2f, want %.2f", test.x0, test.y0, test.x1, test.y1, test.x, y, test.y)
}
}
}

View file

@ -0,0 +1,161 @@
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build darwin || linux || windows
// Package glsprite implements a sprite Engine using OpenGL ES 2.
//
// Each sprite.Texture is loaded as a GL texture object and drawn
// to the screen via an affine transform done in a simple shader.
package glsprite
import (
"image"
"image/draw"
"golang.org/x/mobile/event/size"
"golang.org/x/mobile/exp/f32"
"golang.org/x/mobile/exp/gl/glutil"
"golang.org/x/mobile/exp/sprite"
"golang.org/x/mobile/exp/sprite/clock"
"golang.org/x/mobile/geom"
)
type node struct {
// TODO: move this into package sprite as Node.EngineFields.RelTransform??
relTransform f32.Affine
}
type texture struct {
e *engine
glImage *glutil.Image
b image.Rectangle
}
func (t *texture) Bounds() (w, h int) { return t.b.Dx(), t.b.Dy() }
func (t *texture) Download(r image.Rectangle, dst draw.Image) {
panic("TODO")
}
func (t *texture) Upload(r image.Rectangle, src image.Image) {
draw.Draw(t.glImage.RGBA, r, src, src.Bounds().Min, draw.Src)
t.glImage.Upload()
}
func (t *texture) Release() {
t.glImage.Release()
delete(t.e.textures, t)
}
// Engine creates an OpenGL-based sprite.Engine.
func Engine(images *glutil.Images) sprite.Engine {
return &engine{
nodes: []*node{nil},
images: images,
textures: make(map[*texture]struct{}),
}
}
type engine struct {
images *glutil.Images
textures map[*texture]struct{}
nodes []*node
absTransforms []f32.Affine
}
func (e *engine) Register(n *sprite.Node) {
if n.EngineFields.Index != 0 {
panic("glsprite: sprite.Node already registered")
}
o := &node{}
o.relTransform.Identity()
e.nodes = append(e.nodes, o)
n.EngineFields.Index = int32(len(e.nodes) - 1)
}
func (e *engine) Unregister(n *sprite.Node) {
panic("todo")
}
func (e *engine) LoadTexture(src image.Image) (sprite.Texture, error) {
b := src.Bounds()
t := &texture{
e: e,
glImage: e.images.NewImage(b.Dx(), b.Dy()),
b: b,
}
e.textures[t] = struct{}{}
t.Upload(b, src)
// TODO: set "glImage.Pix = nil"?? We don't need the CPU-side image any more.
return t, nil
}
func (e *engine) SetSubTex(n *sprite.Node, x sprite.SubTex) {
n.EngineFields.Dirty = true // TODO: do we need to propagate dirtiness up/down the tree?
n.EngineFields.SubTex = x
}
func (e *engine) SetTransform(n *sprite.Node, m f32.Affine) {
n.EngineFields.Dirty = true // TODO: do we need to propagate dirtiness up/down the tree?
e.nodes[n.EngineFields.Index].relTransform = m
}
func (e *engine) Render(scene *sprite.Node, t clock.Time, sz size.Event) {
e.absTransforms = append(e.absTransforms[:0], f32.Affine{
{1, 0, 0},
{0, 1, 0},
})
e.render(scene, t, sz)
}
func (e *engine) render(n *sprite.Node, t clock.Time, sz size.Event) {
if n.EngineFields.Index == 0 {
panic("glsprite: sprite.Node not registered")
}
if n.Arranger != nil {
n.Arranger.Arrange(e, n, t)
}
// Push absTransforms.
// TODO: cache absolute transforms and use EngineFields.Dirty?
rel := &e.nodes[n.EngineFields.Index].relTransform
m := f32.Affine{}
m.Mul(&e.absTransforms[len(e.absTransforms)-1], rel)
e.absTransforms = append(e.absTransforms, m)
if x := n.EngineFields.SubTex; x.T != nil {
x.T.(*texture).glImage.Draw(
sz,
geom.Point{
geom.Pt(m[0][2]),
geom.Pt(m[1][2]),
},
geom.Point{
geom.Pt(m[0][2] + m[0][0]),
geom.Pt(m[1][2] + m[1][0]),
},
geom.Point{
geom.Pt(m[0][2] + m[0][1]),
geom.Pt(m[1][2] + m[1][1]),
},
x.R,
)
}
for c := n.FirstChild; c != nil; c = c.NextSibling {
e.render(c, t, sz)
}
// Pop absTransforms.
e.absTransforms = e.absTransforms[:len(e.absTransforms)-1]
}
func (e *engine) Release() {
for img := range e.textures {
img.Release()
}
}

View file

@ -0,0 +1,205 @@
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package portable
import (
"image"
"image/color"
"image/draw"
"image/png"
"math"
"os"
"runtime"
"testing"
"golang.org/x/mobile/event/size"
"golang.org/x/mobile/exp/f32"
"golang.org/x/mobile/geom"
)
func TestAffine(t *testing.T) {
if runtime.GOOS == "android" || runtime.GOOS == "ios" {
t.Skipf("testdata not available on %s", runtime.GOOS)
}
f, err := os.Open("../../../testdata/testpattern.png")
if err != nil {
t.Fatal(err)
}
defer f.Close()
srcOrig, _, err := image.Decode(f)
if err != nil {
t.Fatal(err)
}
src := image.NewRGBA(srcOrig.Bounds())
draw.Draw(src, src.Rect, srcOrig, srcOrig.Bounds().Min, draw.Src)
const (
pixW = 100
pixH = 100
ptW = geom.Pt(50)
ptH = geom.Pt(50)
)
sz := size.Event{
WidthPx: pixW,
HeightPx: pixH,
WidthPt: ptW,
HeightPt: ptH,
PixelsPerPt: float32(pixW) / float32(ptW),
}
got := image.NewRGBA(image.Rect(0, 0, pixW, pixH))
blue := image.NewUniform(color.RGBA{B: 0xff, A: 0xff})
draw.Draw(got, got.Bounds(), blue, image.Point{}, draw.Src)
b := src.Bounds()
b.Min.X += 10
b.Max.Y /= 2
var a f32.Affine
a.Identity()
a.Scale(&a, sz.PixelsPerPt, sz.PixelsPerPt)
a.Translate(&a, 0, 24)
a.Rotate(&a, float32(math.Asin(12./20)))
// See commentary in the render method defined in portable.go.
a.Scale(&a, 40/float32(b.Dx()), 20/float32(b.Dy()))
a.Inverse(&a)
affine(got, src, b, nil, &a, draw.Over)
ptTopLeft := geom.Point{0, 24}
ptBottomRight := geom.Point{12 + 32, 16}
drawCross(got, 0, 0)
drawCross(got, int(ptTopLeft.X.Px(sz.PixelsPerPt)), int(ptTopLeft.Y.Px(sz.PixelsPerPt)))
drawCross(got, int(ptBottomRight.X.Px(sz.PixelsPerPt)), int(ptBottomRight.Y.Px(sz.PixelsPerPt)))
drawCross(got, pixW-1, pixH-1)
const wantPath = "../../../testdata/testpattern-window.png"
f, err = os.Open(wantPath)
if err != nil {
t.Fatal(err)
}
defer f.Close()
wantSrc, _, err := image.Decode(f)
if err != nil {
t.Fatal(err)
}
want, ok := wantSrc.(*image.RGBA)
if !ok {
b := wantSrc.Bounds()
want = image.NewRGBA(b)
draw.Draw(want, b, wantSrc, b.Min, draw.Src)
}
if !imageEq(got, want) {
gotPath, err := writeTempPNG("testpattern-window-got", got)
if err != nil {
t.Fatal(err)
}
t.Errorf("got\n%s\nwant\n%s", gotPath, wantPath)
}
}
func TestAffineMask(t *testing.T) {
if runtime.GOOS == "android" || runtime.GOOS == "ios" {
t.Skipf("testdata not available on %s", runtime.GOOS)
}
f, err := os.Open("../../../testdata/testpattern.png")
if err != nil {
t.Fatal(err)
}
defer f.Close()
srcOrig, _, err := image.Decode(f)
if err != nil {
t.Fatal(err)
}
b := srcOrig.Bounds()
src := image.NewRGBA(b)
draw.Draw(src, src.Rect, srcOrig, b.Min, draw.Src)
mask := image.NewAlpha(b)
for y := b.Min.Y; y < b.Max.Y; y++ {
for x := b.Min.X; x < b.Max.X; x++ {
mask.Set(x, y, color.Alpha{A: uint8(x - b.Min.X)})
}
}
want := image.NewRGBA(b)
draw.DrawMask(want, want.Rect, src, b.Min, mask, b.Min, draw.Src)
a := new(f32.Affine)
a.Identity()
got := image.NewRGBA(b)
affine(got, src, b, mask, a, draw.Src)
if !imageEq(got, want) {
gotPath, err := writeTempPNG("testpattern-mask-got", got)
if err != nil {
t.Fatal(err)
}
wantPath, err := writeTempPNG("testpattern-mask-want", want)
if err != nil {
t.Fatal(err)
}
t.Errorf("got\n%s\nwant\n%s", gotPath, wantPath)
}
}
func writeTempPNG(prefix string, m image.Image) (string, error) {
f, err := os.CreateTemp("", prefix+"-")
if err != nil {
return "", err
}
f.Close()
path := f.Name() + ".png"
f, err = os.Create(path)
if err != nil {
return "", err
}
if err := png.Encode(f, m); err != nil {
f.Close()
return "", err
}
return path, f.Close()
}
func drawCross(m *image.RGBA, x, y int) {
c := color.RGBA{0xff, 0, 0, 0xff} // red
m.SetRGBA(x+0, y-2, c)
m.SetRGBA(x+0, y-1, c)
m.SetRGBA(x-2, y+0, c)
m.SetRGBA(x-1, y+0, c)
m.SetRGBA(x+0, y+0, c)
m.SetRGBA(x+1, y+0, c)
m.SetRGBA(x+2, y+0, c)
m.SetRGBA(x+0, y+1, c)
m.SetRGBA(x+0, y+2, c)
}
func eqEpsilon(x, y uint8) bool {
const epsilon = 9
return x-y < epsilon || y-x < epsilon
}
func colorEq(c0, c1 color.RGBA) bool {
return eqEpsilon(c0.R, c1.R) && eqEpsilon(c0.G, c1.G) && eqEpsilon(c0.B, c1.B) && eqEpsilon(c0.A, c1.A)
}
func imageEq(m0, m1 *image.RGBA) bool {
b0 := m0.Bounds()
b1 := m1.Bounds()
if b0 != b1 {
return false
}
badPx := 0
for y := b0.Min.Y; y < b0.Max.Y; y++ {
for x := b0.Min.X; x < b0.Max.X; x++ {
c0, c1 := m0.At(x, y).(color.RGBA), m1.At(x, y).(color.RGBA)
if !colorEq(c0, c1) {
badPx++
}
}
}
badFrac := float64(badPx) / float64(b0.Dx()*b0.Dy())
return badFrac < 0.01
}

View file

@ -0,0 +1,193 @@
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package portable implements a sprite Engine using the image package.
//
// It is intended to serve as a reference implementation for testing
// other sprite Engines written against OpenGL, or other more exotic
// modern hardware interfaces.
package portable
import (
"image"
"image/draw"
xdraw "golang.org/x/image/draw"
"golang.org/x/image/math/f64"
"golang.org/x/mobile/event/size"
"golang.org/x/mobile/exp/f32"
"golang.org/x/mobile/exp/sprite"
"golang.org/x/mobile/exp/sprite/clock"
)
// Engine builds a sprite Engine that renders onto dst.
func Engine(dst *image.RGBA) sprite.Engine {
return &engine{
dst: dst,
nodes: []*node{nil},
}
}
type node struct {
// TODO: move this into package sprite as Node.EngineFields.RelTransform??
relTransform f32.Affine
}
type texture struct {
m *image.RGBA
}
func (t *texture) Bounds() (w, h int) {
b := t.m.Bounds()
return b.Dx(), b.Dy()
}
func (t *texture) Download(r image.Rectangle, dst draw.Image) {
draw.Draw(dst, r, t.m, t.m.Bounds().Min, draw.Src)
}
func (t *texture) Upload(r image.Rectangle, src image.Image) {
draw.Draw(t.m, r, src, src.Bounds().Min, draw.Src)
}
func (t *texture) Release() {}
type engine struct {
dst *image.RGBA
nodes []*node
absTransforms []f32.Affine
}
func (e *engine) Register(n *sprite.Node) {
if n.EngineFields.Index != 0 {
panic("portable: sprite.Node already registered")
}
o := &node{}
o.relTransform.Identity()
e.nodes = append(e.nodes, o)
n.EngineFields.Index = int32(len(e.nodes) - 1)
}
func (e *engine) Unregister(n *sprite.Node) {
panic("todo")
}
func (e *engine) LoadTexture(m image.Image) (sprite.Texture, error) {
b := m.Bounds()
w, h := b.Dx(), b.Dy()
t := &texture{m: image.NewRGBA(image.Rect(0, 0, w, h))}
t.Upload(b, m)
return t, nil
}
func (e *engine) SetSubTex(n *sprite.Node, x sprite.SubTex) {
n.EngineFields.Dirty = true // TODO: do we need to propagate dirtiness up/down the tree?
n.EngineFields.SubTex = x
}
func (e *engine) SetTransform(n *sprite.Node, m f32.Affine) {
n.EngineFields.Dirty = true // TODO: do we need to propagate dirtiness up/down the tree?
e.nodes[n.EngineFields.Index].relTransform = m
}
func (e *engine) Render(scene *sprite.Node, t clock.Time, sz size.Event) {
// Affine transforms are done in geom.Pt. When finally drawing
// the geom.Pt onto an image.Image we need to convert to system
// pixels. We scale by sz.PixelsPerPt to do this.
e.absTransforms = append(e.absTransforms[:0], f32.Affine{
{sz.PixelsPerPt, 0, 0},
{0, sz.PixelsPerPt, 0},
})
e.render(scene, t)
}
func (e *engine) render(n *sprite.Node, t clock.Time) {
if n.EngineFields.Index == 0 {
panic("portable: sprite.Node not registered")
}
if n.Arranger != nil {
n.Arranger.Arrange(e, n, t)
}
// Push absTransforms.
// TODO: cache absolute transforms and use EngineFields.Dirty?
rel := &e.nodes[n.EngineFields.Index].relTransform
m := f32.Affine{}
m.Mul(&e.absTransforms[len(e.absTransforms)-1], rel)
e.absTransforms = append(e.absTransforms, m)
if x := n.EngineFields.SubTex; x.T != nil {
// Affine transforms work in geom.Pt, which is entirely
// independent of the number of pixels in a texture. A texture
// of any image.Rectangle bounds rendered with
//
// Affine{{1, 0, 0}, {0, 1, 0}}
//
// should have the dimensions (1pt, 1pt). To do this we divide
// by the pixel width and height, reducing the texture to
// (1px, 1px) of the destination image. Multiplying by
// sz.PixelsPerPt, done in Render above, makes it (1pt, 1pt).
dx, dy := x.R.Dx(), x.R.Dy()
if dx > 0 && dy > 0 {
m.Scale(&m, 1/float32(dx), 1/float32(dy))
// TODO(nigeltao): delete the double-inverse: one here and one
// inside func affine.
m.Inverse(&m) // See the documentation on the affine function.
affine(e.dst, x.T.(*texture).m, x.R, nil, &m, draw.Over)
}
}
for c := n.FirstChild; c != nil; c = c.NextSibling {
e.render(c, t)
}
// Pop absTransforms.
e.absTransforms = e.absTransforms[:len(e.absTransforms)-1]
}
func (e *engine) Release() {}
// affine draws each pixel of dst using bilinear interpolation of the
// affine-transformed position in src. This is equivalent to:
//
// for each (x,y) in dst:
// dst(x,y) = bilinear interpolation of src(a*(x,y))
//
// While this is the simpler implementation, it can be counter-
// intuitive as an affine transformation is usually described in terms
// of the source, not the destination. For example, a scale transform
//
// Affine{{2, 0, 0}, {0, 2, 0}}
//
// will produce a dst that is half the size of src. To perform a
// traditional affine transform, use the inverse of the affine matrix.
func affine(dst *image.RGBA, src image.Image, srcb image.Rectangle, mask image.Image, a *f32.Affine, op draw.Op) {
// For legacy compatibility reasons, the matrix a transforms from dst-space
// to src-space. The golang.org/x/image/draw package's matrices transform
// from src-space to dst-space, so we invert (and adjust for different
// origins).
i := *a
i[0][2] += float32(srcb.Min.X)
i[1][2] += float32(srcb.Min.Y)
i.Inverse(&i)
i[0][2] += float32(dst.Rect.Min.X)
i[1][2] += float32(dst.Rect.Min.Y)
m := f64.Aff3{
float64(i[0][0]),
float64(i[0][1]),
float64(i[0][2]),
float64(i[1][0]),
float64(i[1][1]),
float64(i[1][2]),
}
// TODO(nigeltao): is the caller or callee responsible for detecting
// transforms that are simple copies or scales, for which there are faster
// implementations in the xdraw package.
xdraw.ApproxBiLinear.Transform(dst, m, src, srcb, op, &xdraw.Options{
SrcMask: mask,
})
}

126
exp/sprite/sprite.go Normal file
View file

@ -0,0 +1,126 @@
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package sprite provides a 2D scene graph for rendering and animation.
//
// A tree of nodes is drawn by a rendering Engine, provided by another
// package. The OS-independent Go version based on the image package is:
//
// golang.org/x/mobile/exp/sprite/portable
//
// An Engine draws a screen starting at a root Node. The tree is walked
// depth-first, with affine transformations applied at each level.
//
// Nodes are rendered relative to their parent.
//
// Typical main loop:
//
// for each frame {
// quantize time.Now() to a clock.Time
// process UI events
// modify the scene's nodes and animations (Arranger values)
// e.Render(scene, t, sz)
// }
package sprite
import (
"image"
"image/draw"
"golang.org/x/mobile/event/size"
"golang.org/x/mobile/exp/f32"
"golang.org/x/mobile/exp/sprite/clock"
)
type Arranger interface {
Arrange(e Engine, n *Node, t clock.Time)
}
type Texture interface {
Bounds() (w, h int)
Download(r image.Rectangle, dst draw.Image)
Upload(r image.Rectangle, src image.Image)
Release()
}
type SubTex struct {
T Texture
R image.Rectangle
}
type Engine interface {
Register(n *Node)
Unregister(n *Node)
LoadTexture(a image.Image) (Texture, error)
SetSubTex(n *Node, x SubTex)
SetTransform(n *Node, m f32.Affine) // sets transform relative to parent.
// Render renders the scene arranged at the given time, for the given
// window configuration (dimensions and resolution).
Render(scene *Node, t clock.Time, sz size.Event)
Release()
}
// A Node is a renderable element and forms a tree of Nodes.
type Node struct {
Parent, FirstChild, LastChild, PrevSibling, NextSibling *Node
Arranger Arranger
// EngineFields contains fields that should only be accessed by Engine
// implementations. It is exported because such implementations can be
// in other packages.
EngineFields struct {
// TODO: separate TexDirty and TransformDirty bits?
Dirty bool
Index int32
SubTex SubTex
}
}
// AppendChild adds a node c as a child of n.
//
// It will panic if c already has a parent or siblings.
func (n *Node) AppendChild(c *Node) {
if c.Parent != nil || c.PrevSibling != nil || c.NextSibling != nil {
panic("sprite: AppendChild called for an attached child Node")
}
last := n.LastChild
if last != nil {
last.NextSibling = c
} else {
n.FirstChild = c
}
n.LastChild = c
c.Parent = n
c.PrevSibling = last
}
// RemoveChild removes a node c that is a child of n. Afterwards, c will have
// no parent and no siblings.
//
// It will panic if c's parent is not n.
func (n *Node) RemoveChild(c *Node) {
if c.Parent != n {
panic("sprite: RemoveChild called for a non-child Node")
}
if n.FirstChild == c {
n.FirstChild = c.NextSibling
}
if c.NextSibling != nil {
c.NextSibling.PrevSibling = c.PrevSibling
}
if n.LastChild == c {
n.LastChild = c.PrevSibling
}
if c.PrevSibling != nil {
c.PrevSibling.NextSibling = c.NextSibling
}
c.Parent = nil
c.PrevSibling = nil
c.NextSibling = nil
}