Skip to content

Commit

Permalink
clean up tests and add operation tests
Browse files Browse the repository at this point in the history
  • Loading branch information
soypat committed Nov 10, 2024
1 parent 6e2c179 commit 0844734
Showing 1 changed file with 239 additions and 13 deletions.
252 changes: 239 additions & 13 deletions gsdf_gpu_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@ import (
"fmt"
"log"
"math"
"math/rand"
"os"
"runtime"
"testing"

"github.com/chewxy/math32"
"github.com/soypat/glgl/math/ms1"
"github.com/soypat/glgl/math/ms2"
"github.com/soypat/glgl/math/ms3"
"github.com/soypat/glgl/v4.6-core/glgl"
Expand All @@ -28,16 +30,42 @@ type shaderTestConfig struct {
vp gleval.VecPool
prog glbuild.Programmer
progbuf bytes.Buffer
rng *rand.Rand
}

func (cfg *shaderTestConfig) div3(bounds ms3.Box) (int, int, int) {
sz := bounds.Size()
nx, ny, nz := cfg.div(sz.X), cfg.div(sz.Y), cfg.div(sz.Z)
return nx, ny, nz
}
func (cfg *shaderTestConfig) div2(bounds ms2.Box) (int, int) {
sz := bounds.Size()
nx, ny := cfg.div(sz.X), cfg.div(sz.Y)
return nx, ny
}
func (cfg *shaderTestConfig) div(dim float32) int {
divs := dim / cfg.testres
return int(ms1.Clamp(divs, 5, 32))
}

// Since GPU must be run in main thread we need to do some dark arts for GPU code to be code-covered.
func TestMain(m *testing.M) {
err := testGsdfGPU()
if err != nil {
log.Fatal(err)
}
os.Exit(0) // Remove after actual tests added. Is here to prevent "[no tests to run]" message.
os.Exit(m.Run())
}

func testGsdfGPU() error {
const bufsize = 32 * 32 * 32
runtime.LockOSThread()
term, err := gleval.Init1x1GLFW()
if err != nil {
log.Fatal(err)
}
defer term()
invoc := glgl.MaxComputeInvocations()
prog := *glbuild.NewDefaultProgrammer()
prog.SetComputeInvocations(invoc, 1, 1)
Expand All @@ -47,22 +75,30 @@ func TestMain(m *testing.M) {
distbuf: [2][]float32{make([]float32, bufsize), make([]float32, bufsize)},
testres: 1. / 3,
prog: prog,
rng: rand.New(rand.NewSource(1)),
}
t := &tb{}
testPrimitives3D(t, cfg)
if t.fail {
os.Exit(1)
return errors.New("primitives3D failed")
}
testPrimitives2D(t, cfg)
if t.fail {
os.Exit(1)
return errors.New("primitives2D failed")
}
os.Exit(0) // Remove after actual tests added. Is here to prevent "[no tests to run]" message.
exit := func() int {
defer term()
return m.Run()
}()
os.Exit(exit)
testBinOp3D(t, cfg)
if t.fail {
return errors.New("op3D failed")
}
testRandomUnary3D(t, cfg)
if t.fail {
return errors.New("randomUnary3D failed")
}
testBinary2D(t, cfg)
if t.fail {
return errors.New("binary2D failed")
}
return nil
}

func testPrimitives3D(t *tb, cfg *shaderTestConfig) {
Expand All @@ -82,6 +118,62 @@ func testPrimitives3D(t *tb, cfg *shaderTestConfig) {
}
}

func testBinOp3D(t *tb, cfg *shaderTestConfig) {
unionBin := func(a, b glbuild.Shader3D) glbuild.Shader3D {
return Union(a, b)
}
var BinaryOps = []func(a, b glbuild.Shader3D) glbuild.Shader3D{
unionBin,
Difference,
Intersection,
Xor,
}
var smoothOps = []func(k float32, a, b glbuild.Shader3D) glbuild.Shader3D{
SmoothUnion,
SmoothDifference,
SmoothIntersect,
}

s1, _ := NewSphere(1)
s2, _ := NewBox(1, 0.6, .8, 0.1)
s2 = Translate(s2, 0.5, 0.7, 0.8)
for _, op := range BinaryOps {
result := op(s1, s2)
testShader3D(t, result, cfg)
}
for _, op := range smoothOps {
result := op(0.1, s1, s2)
testShader3D(t, result, cfg)
}
}

func testRandomUnary3D(t *tb, cfg *shaderTestConfig) {
var UnaryRandomizedOps = []func(a glbuild.Shader3D, rng *rand.Rand) glbuild.Shader3D{
randomRotation,
randomShell,
randomElongate,
randomRound,
randomScale,
randomSymmetry,
randomTranslate,
// randomArray, // round() differs from go's math.Round()
}
var OtherUnaryRandomizedOps2D3D = []func(a glbuild.Shader2D, rng *rand.Rand) glbuild.Shader3D{
randomExtrude,
randomRevolve,
}
s2, _ := NewBox(1, 0.61, 0.8, 0.3)
for _, op := range UnaryRandomizedOps {
result := op(s2, cfg.rng)
testShader3D(t, result, cfg)
}
s2d := &rect2D{d: ms2.Vec{X: 1, Y: 0.57}}
for _, op := range OtherUnaryRandomizedOps2D3D {
result := op(s2d, cfg.rng)
testShader3D(t, result, cfg)
}
}

func testPrimitives2D(t *tb, cfg *shaderTestConfig) {
const maxdim float32 = 1.0
dimVec := ms2.Vec{X: maxdim, Y: maxdim * 0.47}
Expand All @@ -99,11 +191,29 @@ func testPrimitives2D(t *tb, cfg *shaderTestConfig) {
}
}

func testBinary2D(t *tb, cfg *shaderTestConfig) {
union := func(a, b glbuild.Shader2D) glbuild.Shader2D {
return Union2D(a, b)
}
s2, _ := NewRectangle(1, 0.61)
s1, _ := NewCircle(0.4)
s1 = Translate2D(s1, 0.45, 1)
var BinaryOps2D = []func(a, b glbuild.Shader2D) glbuild.Shader2D{
union,
Difference2D,
Intersection2D,
Xor2D,
}
for _, op := range BinaryOps2D {
result := op(s1, s2)
testShader2D(t, result, cfg)
}
}

func testShader3D(t *tb, obj glbuild.Shader3D, cfg *shaderTestConfig) {
bounds := obj.Bounds()
invocx, _, _ := cfg.prog.ComputeInvocations()
sz := bounds.Size()
nx, ny, nz := int(math32.Max(sz.X/cfg.testres, 3))+1, int(math32.Max(sz.Y/cfg.testres, 3))+1, int(math32.Max(sz.Z/cfg.testres, 3))+1
nx, ny, nz := cfg.div3(bounds)

pos := ms3.AppendGrid(cfg.posbuf[:0], bounds, nx, ny, nz)
distCPU := cfg.distbuf[0][:len(pos)]
Expand All @@ -118,7 +228,6 @@ func testShader3D(t *tb, obj glbuild.Shader3D, cfg *shaderTestConfig) {
if err != nil {
t.Fatal(err)
}

// Do GPU evaluation.
cfg.progbuf.Reset()
n, objs, err := cfg.prog.WriteComputeSDF3(&cfg.progbuf, obj)
Expand Down Expand Up @@ -149,8 +258,7 @@ func testShader3D(t *tb, obj glbuild.Shader3D, cfg *shaderTestConfig) {
func testShader2D(t *tb, obj glbuild.Shader2D, cfg *shaderTestConfig) {
bounds := obj.Bounds()
invocx, _, _ := cfg.prog.ComputeInvocations()
sz := bounds.Size()
nx, ny := int(math32.Max(sz.X/cfg.testres, 3))+1, int(math32.Max(sz.Y/cfg.testres, 3))+1
nx, ny := cfg.div2(bounds)

pos := ms2.AppendGrid(cfg.posbuf2[:0], bounds, nx, ny)
distCPU := cfg.distbuf[0][:len(pos)]
Expand Down Expand Up @@ -261,3 +369,121 @@ func (t *tb) Fatalf(msg string, args ...any) {
t.fail = true
log.Fatalf(msg, args...)
}

func randomCircArray2D(a glbuild.Shader2D, rng *rand.Rand) glbuild.Shader2D {
circleDiv := rng.Intn(16) + 3
nInst := rng.Intn(circleDiv) + 1
s, err := CircularArray2D(a, nInst, circleDiv)
if err != nil {
panic(err)
}
return s
}

func randomRotation(a glbuild.Shader3D, rng *rand.Rand) glbuild.Shader3D {
var axis ms3.Vec
for ms3.Norm(axis) < .5 {
axis = ms3.Vec{X: rng.Float32() * 3, Y: rng.Float32() * 3, Z: rng.Float32() * 3}
}
const maxAngle = 3.14159
var angle float32
for math32.Abs(angle) < 1e-1 || math32.Abs(angle) > 1 {
angle = 2 * maxAngle * (rng.Float32() - 0.5)
}
a, err := Rotate(a, angle, axis)
if err != nil {
panic(err)
}
return a
}

func randomShell(a glbuild.Shader3D, rng *rand.Rand) glbuild.Shader3D {
bb := a.Bounds()
size := bb.Size()
maxSize := bb.Size().Max() / 128
thickness := math32.Min(maxSize, rng.Float32())
if thickness <= 1e-8 {
thickness = math32.Min(maxSize, rng.Float32())
}
shell := Shell(a, thickness)
// Cut shell to visualize interior.

center := bb.Center()
bb.Max.Y = center.Y

halfbox, _ := NewBox(size.X*20, size.Y/3, size.Z*20, 0)
halfbox = Translate(halfbox, 0, size.Y/3, 0)
return Difference(shell, halfbox)
}

func randomArray(a glbuild.Shader3D, rng *rand.Rand) glbuild.Shader3D {
const minDim = 0.1
const maxRepeat = 8
nx, ny, nz := rng.Intn(maxRepeat)+1, rng.Intn(maxRepeat)+1, rng.Intn(maxRepeat)+1
dx, dy, dz := rng.Float32()+minDim, rng.Float32()+minDim, rng.Float32()+minDim
s, err := Array(a, dx, dy, dz, nx, ny, nz)
if err != nil {
panic(err)
}
return s
}

func randomElongate(a glbuild.Shader3D, rng *rand.Rand) glbuild.Shader3D {
const minDim = 0.0
const maxDim = 0.3
const dim = maxDim - minDim
dx, dy, dz := dim*rng.Float32()+minDim, dim*rng.Float32()+minDim, dim*rng.Float32()+minDim
return Elongate(a, dx, dy, dz)
}

func randomRound(a glbuild.Shader3D, rng *rand.Rand) glbuild.Shader3D {
bb := a.Bounds().Size()
minround := bb.Min() / 64
maxround := bb.Min() / 2
round := minround + (rng.Float32() * (maxround - minround))
return Offset(a, -round)
}

func randomTranslate(a glbuild.Shader3D, rng *rand.Rand) glbuild.Shader3D {
var p ms3.Vec
for ms3.Norm(p) < 0.1 {
p = ms3.Vec{X: rng.Float32(), Y: rng.Float32(), Z: rng.Float32()}
p = ms3.Scale((rng.Float32()-0.5)*4, p)
}

return Translate(a, p.X, p.Y, p.Z)
}

func randomSymmetry(a glbuild.Shader3D, rng *rand.Rand) glbuild.Shader3D {
q := rng.Uint32()
for q&0b111 == 0 {
q = rng.Uint32()
}
x := q&(1<<0) != 0
y := q&(1<<1) != 0
z := q&(1<<2) != 0
return Symmetry(a, x, y, z)
}

func randomScale(a glbuild.Shader3D, rng *rand.Rand) glbuild.Shader3D {
const minScale, maxScale = 0.01, 100.
scale := minScale + rng.Float32()*(maxScale-minScale)
return Scale(a, scale)
}

func randomExtrude(a glbuild.Shader2D, rng *rand.Rand) glbuild.Shader3D {
const minheight, maxHeight = 0.01, 40.
height := minheight + rng.Float32()*(maxHeight-minheight)
ex, _ := Extrude(a, height)
return ex
}

func randomRevolve(a glbuild.Shader2D, rng *rand.Rand) glbuild.Shader3D {
const minOff, maxOff = 0, 40.
off := minOff + rng.Float32()*(maxOff-minOff)
rev, err := Revolve(a, off)
if err != nil {
panic(err)
}
return rev
}

0 comments on commit 0844734

Please sign in to comment.