Skip to content

Commit

Permalink
gpu: major update to arrayn variable creation: actual array size is d…
Browse files Browse the repository at this point in the history
…etermined when value buffer is set, and also added ReadOnly flag, so it auto-creates read buffer for all !ReadOnly Storage variables.
  • Loading branch information
rcoreilly committed Oct 4, 2024
1 parent 3e33b22 commit fdb620f
Show file tree
Hide file tree
Showing 12 changed files with 197 additions and 75 deletions.
1 change: 1 addition & 0 deletions goal/gosl/examples/basic/compute.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import "cogentcore.org/core/math32"
//gosl:vars
var (
// Params are the parameters for the computation.
//gosl:read-only
Params []ParamStruct

// Data is the data on which the computation operates.
Expand Down
137 changes: 137 additions & 0 deletions goal/gosl/examples/basic/gosl.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 4 additions & 2 deletions goal/gosl/examples/basic/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"runtime"

"cogentcore.org/core/base/timer"
"cogentcore.org/core/gpu"
)

//go:generate gosl .
Expand All @@ -22,6 +23,9 @@ func init() {
}

func main() {
gpu.Debug = true
GPUInit()

n := 2000000 // note: not necc to spec up-front, but easier if so

Params = make([]ParamStruct, 1)
Expand All @@ -37,8 +41,6 @@ func main() {
sd[i].Raw = Data[i].Raw
}

GPUInit()

cpuTmr := timer.Time{}
cpuTmr.Start()

Expand Down
2 changes: 0 additions & 2 deletions goal/gosl/examples/rand/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,6 @@ func main() {
gpu.SetValueFrom(cvl, []uint64{seed})
gpu.SetValueFrom(dvl, dataG)

sgp.CreateReadBuffers()

gpuTmr := timer.Time{}
gpuTmr.Start()

Expand Down
19 changes: 9 additions & 10 deletions goal/gosl/gotosl/gengpu.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ import (
//go:embed %s/*.wgsl
var shaders embed.FS
// GPU is the compute gpu device
// ComputeGPU is the compute gpu device
var ComputeGPU *gpu.GPU
// UseGPU indicates whether to use GPU vs. CPU.
Expand All @@ -53,6 +53,7 @@ var UseGPU bool
b.WriteString(fmt.Sprintf(header, st.Config.Output))

for _, sy := range st.Systems {
b.WriteString(fmt.Sprintf("// %s is a GPU compute System with kernels operating on the\nsame set of data variables.\n", st.genSysVar(sy)))
b.WriteString(fmt.Sprintf("var %s *gpu.ComputeSystem\n", st.genSysVar(sy)))
}

Expand All @@ -77,8 +78,8 @@ const (
b.WriteString(")\n")

initf := `
// GPUInit initializes the GPU compute system
// Configuring Systems, variables and kernels.
// GPUInit initializes the GPU compute system,
// configuring system(s), variables and kernels.
func GPUInit() {
gp := gpu.NewComputeGPU()
ComputeGPU = gp
Expand All @@ -92,7 +93,8 @@ func GPUInit() {
b.WriteString("}\n\n")

release := `
// GPURelease releases the GPU compute system.
// GPURelease releases the GPU compute system resources.
// Call this at program exit.
func GPURelease() {
`

Expand Down Expand Up @@ -138,9 +140,6 @@ func (st *State) GenGPUSystemInit(sy *System) string {
b.WriteString(fmt.Sprintf("\t\t\tsgp.AddStruct(%q, int(unsafe.Sizeof(%s{})), len(%s), gpu.ComputeShader)\n", vr.Name, vr.Type[2:], vr.Name))
}
b.WriteString("\t\t\tsgp.SetNValues(1)\n")
if !gp.Uniform {
b.WriteString("\t\t\tsgp.CreateReadBuffers()\n")
}
b.WriteString("\t\t}\n")
}
b.WriteString("\t\tsy.Config()\n")
Expand All @@ -157,9 +156,9 @@ func (st *State) GenGPUSystemOps(sy *System) string {

// 1 = kernel, 2 = system, 3 = sysname
run := `
// Run%[1]s runs the %[1]s kernel with given number of items,
// on either the CPU or GPU depending on the UseGPU.
// Pass *Var variable names to sync those variables back from the GPU
// Run%[1]s runs the %[1]s kernel with given number of elements,
// on either the CPU or GPU depending on the UseGPU variable.
// Pass *Var variable enums to sync those variables back from the GPU
// after running (irrelevant for CPU).
func Run%[1]s(n int, syncVars ...GPUVars) {
if UseGPU {
Expand Down
5 changes: 5 additions & 0 deletions goal/gosl/gotosl/gotosl.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,11 @@ type Var struct {

// Type of variable: either []Type or tensor.Float32, tensor.Int32
Type string

// ReadOnly indicates that this variable is never read back from GPU,
// specified by the gosl:read-only property in the variable comments.
// It is important to optimize GPU memory usage to indicate this.
ReadOnly bool
}

// Group represents one variable group.
Expand Down
2 changes: 0 additions & 2 deletions gpu/examples/compute/compute.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,6 @@ func main() {
}
gpu.SetValueFrom(dvl, sd)

sgp.CreateReadBuffers()

ce, _ := sy.BeginComputePass()
pl.Dispatch1D(ce, n, threads)
ce.End()
Expand Down
39 changes: 32 additions & 7 deletions gpu/value.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,17 @@ type Value struct {
// index of this value within the Var list of values
Index int

// VarSize is the size of each Var element, which includes any fixed ArrayN
// VarSize is the size of each Var element, which includes any fixed Var.ArrayN
// array size specified on the Var.
// The actual buffer size is VarSize * Value.ArrayN (or DynamicN for dynamic).
VarSize int

// ArrayN is the actual number of array elements, for Uniform or Storage
// variables without a fixed array size (i.e., the Var ArrayN = 1).
// This is set when the buffer is actually created, based on the data,
// or can be set directly prior to buffer creation.
ArrayN int

// DynamicIndex is the current index into a DynamicOffset variable
// to use for the SetBindGroup call. Note that this is an index,
// not an offset, so it indexes the DynamicN Vars in the Value,
Expand Down Expand Up @@ -112,6 +119,7 @@ func (vl *Value) init(vr *Var, dev *Device, idx int) {
vl.Index = idx
vl.Name = fmt.Sprintf("%s_%d", vr.Name, vl.Index)
vl.VarSize = vr.MemSize()
vl.ArrayN = 1
vl.alignBytes = vr.alignBytes
vl.AlignVarSize = MemSizeAlign(vl.VarSize, vl.alignBytes)
vl.isDynamic = vl.role == Vertex || vl.role == Index || vr.DynamicOffset
Expand All @@ -129,11 +137,12 @@ func (vl *Value) MemSize() int {
if vl.isDynamic {
return vl.AlignVarSize * vl.dynamicN
}
return vl.VarSize
return vl.ArrayN * vl.VarSize
}

// CreateBuffer creates the GPU buffer for this value if it does not
// yet exist or is not the right size.
// For !ReadOnly [Storage] buffers, calls [Value.CreateReadBuffer].
func (vl *Value) CreateBuffer() error {
if vl.role == SampledTexture {
return nil
Expand All @@ -159,6 +168,9 @@ func (vl *Value) CreateBuffer() error {
}
vl.AllocSize = sz
vl.buffer = buf
if vl.role == Storage && !vl.vvar.ReadOnly {
vl.CreateReadBuffer()
}
return nil
}

Expand Down Expand Up @@ -214,6 +226,9 @@ func (vl *Value) SetDynamicN(n int) {

// SetValueFrom copies given values into value buffer memory,
// making the buffer if it has not yet been constructed.
// The actual ArrayN size of Storage or Uniform variables will
// be computed based on the size of the from bytes, relative to
// the variable size.
// IMPORTANT: do not use this for dynamic offset Uniform or
// Storage variables, as the alignment will not be correct;
// See [SetDynamicFromBytes].
Expand All @@ -223,6 +238,7 @@ func SetValueFrom[E any](vl *Value, from []E) error {

// SetFromBytes copies given bytes into value buffer memory,
// making the buffer if it has not yet been constructed.
// For !ReadOnly [Storage] buffers, calls [Value.CreateReadBuffer].
// IMPORTANT: do not use this for dynamic offset Uniform or
// Storage variables, as the alignment will not be correct;
// See [SetDynamicFromBytes].
Expand All @@ -232,12 +248,19 @@ func (vl *Value) SetFromBytes(from []byte) error {
return errors.Log(err)
}
nb := len(from)
an := nb / vl.VarSize
aover := nb % vl.VarSize
if aover != 0 {
err := fmt.Errorf("gpu.Value SetFromBytes %s, Size passed: %d is not an even multiple of the variable size: %d", vl.Name, nb, vl.VarSize)
return errors.Log(err)
}
if vl.isDynamic { // Vertex, Index at this point
dn := nb / vl.VarSize
vl.SetDynamicN(dn)
vl.SetDynamicN(an)
} else {
vl.ArrayN = an
}
tb := vl.MemSize()
if nb != tb {
if nb != tb { // this should never happen, but justin case
err := fmt.Errorf("gpu.Value SetFromBytes %s, Size passed: %d != Size expected %d", vl.Name, nb, tb)
return errors.Log(err)
}
Expand All @@ -254,6 +277,9 @@ func (vl *Value) SetFromBytes(from []byte) error {
}
vl.buffer = buf
vl.AllocSize = nb
if vl.role == Storage && !vl.vvar.ReadOnly {
vl.CreateReadBuffer()
}
} else {
err := vl.device.Queue.WriteBuffer(vl.buffer, 0, from)
if errors.Log(err) != nil {
Expand Down Expand Up @@ -406,9 +432,8 @@ func (vl *Value) SetFromTexture(tx *Texture) *Texture {
}

// CreateReadBuffer creates a read buffer for this value,
// if it does not yet exist or is not the right size.
// for [Storage] values only. Automatically called for !ReadOnly.
// Read buffer is needed for reading values back from the GPU.
// Only for Storage role variables.
func (vl *Value) CreateReadBuffer() error {
if !(vl.role == Storage || vl.role == StorageTexture) {
return nil
Expand Down
12 changes: 0 additions & 12 deletions gpu/values.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,18 +147,6 @@ func (vs *Values) MemSize() int {
return tsz
}

// CreateReadBuffers creates read buffers for all values.
func (vs *Values) CreateReadBuffers() error {
var errs []error
for _, vl := range vs.Values {
err := vl.CreateReadBuffer()
if err != nil {
errs = append(errs, err)
}
}
return errors.Join(errs...)
}

// bindGroupEntry returns the BindGroupEntry for Current
// value for this variable.
func (vs *Values) bindGroupEntry(vr *Var) []wgpu.BindGroupEntry {
Expand Down
Loading

0 comments on commit fdb620f

Please sign in to comment.