mirror of
https://git.sr.ht/~eliasnaur/gio
synced 2026-07-06 01:45:36 +00:00
gpu: [compute] add CPU fallback
This change adds a CPU fallback for devices that don't support the old renderer nor have GPU support for compute programs. Most of the hard work is implemented in the gioui.org/cpu module. It uses the SwiftShader project with light modification to output statically compiled CPU .o files for each compute program. The CPU fallback only covers Linux and Android on arm, arm64, amd64 architectures. There is no fundamental reason support can't be extended to other platforms: - macOS and iOS are probably easy, but it's likely that virtually every device has GPU support for compute shaders. - Windows needs a Cgo-less port, or a build constraint to require a C compiler (Gio core doesn't). - FreeBSD and OpenBSD are probably also easy to do because they're so similar to Linux. - The 386 binaries didn't work properly in my tests, so fixes to SwiftShader is probably needed. However, I expect virtually every Intel device can run amd64 binaries. Updates gio#49 Fixes gio#228 Signed-off-by: Elias Naur <mail@eliasnaur.com>
This commit is contained in:
@@ -8,4 +8,4 @@ require (
|
|||||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c
|
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c
|
||||||
)
|
)
|
||||||
|
|
||||||
require golang.org/x/text v0.3.6 // indirect
|
require gioui.org/cpu v0.0.0-20210727122813-41509bcd3462
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20201218220906-28db891af037/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
dmitri.shuralyov.com/gpu/mtl v0.0.0-20201218220906-28db891af037/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||||
|
gioui.org/cpu v0.0.0-20210727122813-41509bcd3462 h1:JZyB+d8tPExZHNZwMiGKeeAVd0mkFTc3Zsmegdn178M=
|
||||||
|
gioui.org/cpu v0.0.0-20210727122813-41509bcd3462/go.mod h1:DkhBDuHokSMOUxX5LZQ7IcxyJJzs3OON8Z5ojaXUXxo=
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||||
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
|
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
|
||||||
|
|||||||
+288
-102
@@ -14,6 +14,7 @@ import (
|
|||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"math"
|
"math"
|
||||||
"math/bits"
|
"math/bits"
|
||||||
|
"runtime"
|
||||||
"sort"
|
"sort"
|
||||||
"time"
|
"time"
|
||||||
"unsafe"
|
"unsafe"
|
||||||
@@ -28,6 +29,9 @@ import (
|
|||||||
"gioui.org/layout"
|
"gioui.org/layout"
|
||||||
"gioui.org/op"
|
"gioui.org/op"
|
||||||
"gioui.org/op/clip"
|
"gioui.org/op/clip"
|
||||||
|
|
||||||
|
"gioui.org/cpu"
|
||||||
|
"gioui.org/cpu/piet"
|
||||||
)
|
)
|
||||||
|
|
||||||
type compute struct {
|
type compute struct {
|
||||||
@@ -40,16 +44,16 @@ type compute struct {
|
|||||||
maxTextureDim int
|
maxTextureDim int
|
||||||
|
|
||||||
programs struct {
|
programs struct {
|
||||||
elements driver.Program
|
elements computeProgram
|
||||||
tileAlloc driver.Program
|
tileAlloc computeProgram
|
||||||
pathCoarse driver.Program
|
pathCoarse computeProgram
|
||||||
backdrop driver.Program
|
backdrop computeProgram
|
||||||
binning driver.Program
|
binning computeProgram
|
||||||
coarse driver.Program
|
coarse computeProgram
|
||||||
kernel4 driver.Program
|
kernel4 computeProgram
|
||||||
}
|
}
|
||||||
buffers struct {
|
buffers struct {
|
||||||
config driver.Buffer
|
config sizedBuffer
|
||||||
scene sizedBuffer
|
scene sizedBuffer
|
||||||
state sizedBuffer
|
state sizedBuffer
|
||||||
memory sizedBuffer
|
memory sizedBuffer
|
||||||
@@ -66,6 +70,8 @@ type compute struct {
|
|||||||
layerVertices []layerVertex
|
layerVertices []layerVertex
|
||||||
layerAtlases []*layerAtlas
|
layerAtlases []*layerAtlas
|
||||||
packer packer
|
packer packer
|
||||||
|
|
||||||
|
descriptors *piet.Kernel4DescriptorSetLayout
|
||||||
}
|
}
|
||||||
// images contains ImageOp images packed into a texture atlas.
|
// images contains ImageOp images packed into a texture atlas.
|
||||||
images struct {
|
images struct {
|
||||||
@@ -94,6 +100,12 @@ type compute struct {
|
|||||||
|
|
||||||
uniforms *materialUniforms
|
uniforms *materialUniforms
|
||||||
uniBuf driver.Buffer
|
uniBuf driver.Buffer
|
||||||
|
|
||||||
|
// CPU fields
|
||||||
|
cpuTex cpu.ImageDescriptor
|
||||||
|
// regions track new materials in tex, so they can be transferred to cpuTex.
|
||||||
|
regions []image.Rectangle
|
||||||
|
scratch []byte
|
||||||
}
|
}
|
||||||
timers struct {
|
timers struct {
|
||||||
profile string
|
profile string
|
||||||
@@ -103,6 +115,10 @@ type compute struct {
|
|||||||
blit *timer
|
blit *timer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CPU fallback fields.
|
||||||
|
useCPU bool
|
||||||
|
dispatcher *dispatcher
|
||||||
|
|
||||||
// The following fields hold scratch space to avoid garbage.
|
// The following fields hold scratch space to avoid garbage.
|
||||||
zeroSlice []byte
|
zeroSlice []byte
|
||||||
memHeader *memoryHeader
|
memHeader *memoryHeader
|
||||||
@@ -124,10 +140,11 @@ type layerPlace struct {
|
|||||||
type layerAtlas struct {
|
type layerAtlas struct {
|
||||||
// image is the layer atlas texture. Note that it is in RGBA format,
|
// image is the layer atlas texture. Note that it is in RGBA format,
|
||||||
// but contains data in sRGB. See blitLayers for more detail.
|
// but contains data in sRGB. See blitLayers for more detail.
|
||||||
image driver.Texture
|
image driver.Texture
|
||||||
fbo driver.Framebuffer
|
fbo driver.Framebuffer
|
||||||
size image.Point
|
cpuImage cpu.ImageDescriptor
|
||||||
layers int
|
size image.Point
|
||||||
|
layers int
|
||||||
}
|
}
|
||||||
|
|
||||||
type copyUniforms struct {
|
type copyUniforms struct {
|
||||||
@@ -274,9 +291,22 @@ type encodeState struct {
|
|||||||
clip f32.Rectangle
|
clip f32.Rectangle
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// sizedBuffer holds a GPU buffer, or its equivalent CPU memory.
|
||||||
type sizedBuffer struct {
|
type sizedBuffer struct {
|
||||||
size int
|
size int
|
||||||
buffer driver.Buffer
|
buffer driver.Buffer
|
||||||
|
// cpuBuf is initialized when useCPU is true.
|
||||||
|
cpuBuf cpu.BufferDescriptor
|
||||||
|
}
|
||||||
|
|
||||||
|
// computeProgram holds a compute program, or its equivalent CPU implementation.
|
||||||
|
type computeProgram struct {
|
||||||
|
prog driver.Program
|
||||||
|
|
||||||
|
// CPU fields.
|
||||||
|
progInfo *cpu.ProgramInfo
|
||||||
|
descriptors unsafe.Pointer
|
||||||
|
buffers []*cpu.BufferDescriptor
|
||||||
}
|
}
|
||||||
|
|
||||||
// config matches Config in setup.h
|
// config matches Config in setup.h
|
||||||
@@ -332,7 +362,8 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func newCompute(ctx driver.Device) (*compute, error) {
|
func newCompute(ctx driver.Device) (*compute, error) {
|
||||||
maxDim := ctx.Caps().MaxTextureSize
|
caps := ctx.Caps()
|
||||||
|
maxDim := caps.MaxTextureSize
|
||||||
// Large atlas textures cause artifacts due to precision loss in
|
// Large atlas textures cause artifacts due to precision loss in
|
||||||
// shaders.
|
// shaders.
|
||||||
if cap := 8192; maxDim > cap {
|
if cap := 8192; maxDim > cap {
|
||||||
@@ -344,6 +375,35 @@ func newCompute(ctx driver.Device) (*compute, error) {
|
|||||||
conf: new(config),
|
conf: new(config),
|
||||||
memHeader: new(memoryHeader),
|
memHeader: new(memoryHeader),
|
||||||
}
|
}
|
||||||
|
shaders := []struct {
|
||||||
|
prog *computeProgram
|
||||||
|
src driver.ShaderSources
|
||||||
|
info *cpu.ProgramInfo
|
||||||
|
hash string
|
||||||
|
}{
|
||||||
|
{&g.programs.elements, shader_elements_comp, piet.ElementsProgramInfo, piet.ElementsHash},
|
||||||
|
{&g.programs.tileAlloc, shader_tile_alloc_comp, piet.Tile_allocProgramInfo, piet.Tile_allocHash},
|
||||||
|
{&g.programs.pathCoarse, shader_path_coarse_comp, piet.Path_coarseProgramInfo, piet.Path_coarseHash},
|
||||||
|
{&g.programs.backdrop, shader_backdrop_comp, piet.BackdropProgramInfo, piet.BackdropHash},
|
||||||
|
{&g.programs.binning, shader_binning_comp, piet.BinningProgramInfo, piet.BinningHash},
|
||||||
|
{&g.programs.coarse, shader_coarse_comp, piet.CoarseProgramInfo, piet.CoarseHash},
|
||||||
|
{&g.programs.kernel4, shader_kernel4_comp, piet.Kernel4ProgramInfo, piet.Kernel4Hash},
|
||||||
|
}
|
||||||
|
if !caps.Features.Has(driver.FeatureCompute) {
|
||||||
|
g.useCPU = supportsCPUCompute
|
||||||
|
for _, s := range shaders {
|
||||||
|
if s.src.Hash != s.hash {
|
||||||
|
g.useCPU = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !g.useCPU {
|
||||||
|
return nil, errors.New("gpu: missing support for compute programs")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if g.useCPU {
|
||||||
|
g.dispatcher = newDispatcher(runtime.NumCPU())
|
||||||
|
}
|
||||||
|
|
||||||
// Large enough for reasonable fill sizes, yet still spannable by the compute programs.
|
// Large enough for reasonable fill sizes, yet still spannable by the compute programs.
|
||||||
g.output.packer.maxDim = 4096
|
g.output.packer.maxDim = 4096
|
||||||
@@ -397,32 +457,55 @@ func newCompute(ctx driver.Device) (*compute, error) {
|
|||||||
g.materials.uniBuf = buf
|
g.materials.uniBuf = buf
|
||||||
g.materials.prog.SetVertexUniforms(buf)
|
g.materials.prog.SetVertexUniforms(buf)
|
||||||
|
|
||||||
buf, err = ctx.NewBuffer(driver.BufferBindingShaderStorage, int(unsafe.Sizeof(config{})))
|
|
||||||
if err != nil {
|
|
||||||
g.Release()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
g.buffers.config = buf
|
|
||||||
|
|
||||||
shaders := []struct {
|
|
||||||
prog *driver.Program
|
|
||||||
src driver.ShaderSources
|
|
||||||
}{
|
|
||||||
{&g.programs.elements, shader_elements_comp},
|
|
||||||
{&g.programs.tileAlloc, shader_tile_alloc_comp},
|
|
||||||
{&g.programs.pathCoarse, shader_path_coarse_comp},
|
|
||||||
{&g.programs.backdrop, shader_backdrop_comp},
|
|
||||||
{&g.programs.binning, shader_binning_comp},
|
|
||||||
{&g.programs.coarse, shader_coarse_comp},
|
|
||||||
{&g.programs.kernel4, shader_kernel4_comp},
|
|
||||||
}
|
|
||||||
for _, shader := range shaders {
|
for _, shader := range shaders {
|
||||||
p, err := ctx.NewComputeProgram(shader.src)
|
if !g.useCPU {
|
||||||
if err != nil {
|
p, err := ctx.NewComputeProgram(shader.src)
|
||||||
g.Release()
|
if err != nil {
|
||||||
return nil, err
|
g.Release()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
shader.prog.prog = p
|
||||||
|
} else {
|
||||||
|
shader.prog.progInfo = shader.info
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if g.useCPU {
|
||||||
|
{
|
||||||
|
desc := new(piet.ElementsDescriptorSetLayout)
|
||||||
|
g.programs.elements.descriptors = unsafe.Pointer(desc)
|
||||||
|
g.programs.elements.buffers = []*cpu.BufferDescriptor{desc.Binding0(), desc.Binding1(), desc.Binding2(), desc.Binding3()}
|
||||||
|
}
|
||||||
|
{
|
||||||
|
desc := new(piet.Tile_allocDescriptorSetLayout)
|
||||||
|
g.programs.tileAlloc.descriptors = unsafe.Pointer(desc)
|
||||||
|
g.programs.tileAlloc.buffers = []*cpu.BufferDescriptor{desc.Binding0(), desc.Binding1()}
|
||||||
|
}
|
||||||
|
{
|
||||||
|
desc := new(piet.Path_coarseDescriptorSetLayout)
|
||||||
|
g.programs.pathCoarse.descriptors = unsafe.Pointer(desc)
|
||||||
|
g.programs.pathCoarse.buffers = []*cpu.BufferDescriptor{desc.Binding0(), desc.Binding1()}
|
||||||
|
}
|
||||||
|
{
|
||||||
|
desc := new(piet.BackdropDescriptorSetLayout)
|
||||||
|
g.programs.backdrop.descriptors = unsafe.Pointer(desc)
|
||||||
|
g.programs.backdrop.buffers = []*cpu.BufferDescriptor{desc.Binding0(), desc.Binding1()}
|
||||||
|
}
|
||||||
|
{
|
||||||
|
desc := new(piet.BinningDescriptorSetLayout)
|
||||||
|
g.programs.binning.descriptors = unsafe.Pointer(desc)
|
||||||
|
g.programs.binning.buffers = []*cpu.BufferDescriptor{desc.Binding0(), desc.Binding1()}
|
||||||
|
}
|
||||||
|
{
|
||||||
|
desc := new(piet.CoarseDescriptorSetLayout)
|
||||||
|
g.programs.coarse.descriptors = unsafe.Pointer(desc)
|
||||||
|
g.programs.coarse.buffers = []*cpu.BufferDescriptor{desc.Binding0(), desc.Binding1()}
|
||||||
|
}
|
||||||
|
{
|
||||||
|
desc := new(piet.Kernel4DescriptorSetLayout)
|
||||||
|
g.programs.kernel4.descriptors = unsafe.Pointer(desc)
|
||||||
|
g.programs.kernel4.buffers = []*cpu.BufferDescriptor{desc.Binding0(), desc.Binding1()}
|
||||||
|
g.output.descriptors = desc
|
||||||
}
|
}
|
||||||
*shader.prog = p
|
|
||||||
}
|
}
|
||||||
return g, nil
|
return g, nil
|
||||||
}
|
}
|
||||||
@@ -553,7 +636,7 @@ func (g *compute) compactLayers() error {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
outputSize := g.output.packer.sizes[0]
|
outputSize := g.output.packer.sizes[0]
|
||||||
atlas.ensureSize(g.ctx, outputSize)
|
atlas.ensureSize(g.useCPU, g.ctx, outputSize)
|
||||||
for i, l := range layers[:end] {
|
for i, l := range layers[:end] {
|
||||||
if l.newPlace == l.place {
|
if l.newPlace == l.place {
|
||||||
continue
|
continue
|
||||||
@@ -629,10 +712,10 @@ func (g *compute) renderLayers(viewport image.Point) error {
|
|||||||
Y: (outputSize.Y + tileHeightPx - 1) / tileHeightPx,
|
Y: (outputSize.Y + tileHeightPx - 1) / tileHeightPx,
|
||||||
}
|
}
|
||||||
w, h := tileDims.X*tileWidthPx, tileDims.Y*tileHeightPx
|
w, h := tileDims.X*tileWidthPx, tileDims.Y*tileHeightPx
|
||||||
if err := atlas.ensureSize(g.ctx, image.Pt(w, h)); err != nil {
|
if err := atlas.ensureSize(g.useCPU, g.ctx, image.Pt(w, h)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := g.render(atlas.image, tileDims); err != nil {
|
if err := g.render(atlas.image, atlas.cpuImage, tileDims, atlas.size.X*4); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -694,7 +777,7 @@ func (g *compute) blitLayers(viewport image.Point) {
|
|||||||
g.output.uniforms.uvScale = [2]float32{1 / float32(atlas.size.X), 1 / float32(atlas.size.Y)}
|
g.output.uniforms.uvScale = [2]float32{1 / float32(atlas.size.X), 1 / float32(atlas.size.Y)}
|
||||||
g.output.uniBuf.Upload(byteslice.Struct(g.output.uniforms))
|
g.output.uniBuf.Upload(byteslice.Struct(g.output.uniforms))
|
||||||
vertexData := byteslice.Slice(g.output.layerVertices)
|
vertexData := byteslice.Slice(g.output.layerVertices)
|
||||||
g.output.buffer.ensureCapacity(g.ctx, driver.BufferBindingVertices, len(vertexData))
|
g.output.buffer.ensureCapacity(false, g.ctx, driver.BufferBindingVertices, len(vertexData))
|
||||||
g.output.buffer.buffer.Upload(vertexData)
|
g.output.buffer.buffer.Upload(vertexData)
|
||||||
g.ctx.BindVertexBuffer(g.output.buffer.buffer, int(unsafe.Sizeof(g.output.layerVertices[0])), 0)
|
g.ctx.BindVertexBuffer(g.output.buffer.buffer, int(unsafe.Sizeof(g.output.layerVertices[0])), 0)
|
||||||
g.ctx.BindTexture(0, atlas.image)
|
g.ctx.BindTexture(0, atlas.image)
|
||||||
@@ -705,6 +788,7 @@ func (g *compute) blitLayers(viewport image.Point) {
|
|||||||
func (g *compute) renderMaterials() error {
|
func (g *compute) renderMaterials() error {
|
||||||
m := &g.materials
|
m := &g.materials
|
||||||
m.quads = m.quads[:0]
|
m.quads = m.quads[:0]
|
||||||
|
m.regions = m.regions[:0]
|
||||||
resize := false
|
resize := false
|
||||||
reclaimed := false
|
reclaimed := false
|
||||||
restart:
|
restart:
|
||||||
@@ -752,6 +836,10 @@ restart:
|
|||||||
}
|
}
|
||||||
m.offsets[op.key] = offset
|
m.offsets[op.key] = offset
|
||||||
g.enc.setFillImageOffset(op.sceneIdx, offset.Sub(op.off))
|
g.enc.setFillImageOffset(op.sceneIdx, offset.Sub(op.off))
|
||||||
|
m.regions = append(m.regions, image.Rectangle{
|
||||||
|
Min: place.Pos,
|
||||||
|
Max: place.Pos.Add(size),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -768,6 +856,7 @@ restart:
|
|||||||
m.tex.Release()
|
m.tex.Release()
|
||||||
m.tex = nil
|
m.tex = nil
|
||||||
}
|
}
|
||||||
|
m.cpuTex.Free()
|
||||||
handle, err := g.ctx.NewTexture(driver.TextureFormatRGBA8, texSize, texSize,
|
handle, err := g.ctx.NewTexture(driver.TextureFormatRGBA8, texSize, texSize,
|
||||||
driver.FilterNearest, driver.FilterNearest,
|
driver.FilterNearest, driver.FilterNearest,
|
||||||
driver.BufferBindingShaderStorage|driver.BufferBindingFramebuffer)
|
driver.BufferBindingShaderStorage|driver.BufferBindingFramebuffer)
|
||||||
@@ -781,6 +870,9 @@ restart:
|
|||||||
}
|
}
|
||||||
m.tex = handle
|
m.tex = handle
|
||||||
m.fbo = fbo
|
m.fbo = fbo
|
||||||
|
if g.useCPU {
|
||||||
|
m.cpuTex = cpu.NewImageRGBA(texSize, texSize)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Transform to clip space: [-1, -1] - [1, 1].
|
// Transform to clip space: [-1, -1] - [1, 1].
|
||||||
g.materials.uniforms.scale = [2]float32{2 / float32(texSize), 2 / float32(texSize)}
|
g.materials.uniforms.scale = [2]float32{2 / float32(texSize), 2 / float32(texSize)}
|
||||||
@@ -788,7 +880,7 @@ restart:
|
|||||||
g.materials.uniBuf.Upload(byteslice.Struct(g.materials.uniforms))
|
g.materials.uniBuf.Upload(byteslice.Struct(g.materials.uniforms))
|
||||||
vertexData := byteslice.Slice(m.quads)
|
vertexData := byteslice.Slice(m.quads)
|
||||||
n := pow2Ceil(len(vertexData))
|
n := pow2Ceil(len(vertexData))
|
||||||
m.buffer.ensureCapacity(g.ctx, driver.BufferBindingVertices, n)
|
m.buffer.ensureCapacity(false, g.ctx, driver.BufferBindingVertices, n)
|
||||||
m.buffer.buffer.Upload(vertexData)
|
m.buffer.buffer.Upload(vertexData)
|
||||||
g.ctx.BindTexture(0, g.images.tex)
|
g.ctx.BindTexture(0, g.images.tex)
|
||||||
g.ctx.BindFramebuffer(m.fbo)
|
g.ctx.BindFramebuffer(m.fbo)
|
||||||
@@ -962,7 +1054,7 @@ func (enc *encoder) encodePath(verts []byte) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *compute) render(dst driver.Texture, tileDims image.Point) error {
|
func (g *compute) render(dst driver.Texture, cpuDst cpu.ImageDescriptor, tileDims image.Point, stride int) error {
|
||||||
const (
|
const (
|
||||||
// wgSize is the largest and most common workgroup size.
|
// wgSize is the largest and most common workgroup size.
|
||||||
wgSize = 128
|
wgSize = 128
|
||||||
@@ -985,16 +1077,11 @@ func (g *compute) render(dst driver.Texture, tileDims image.Point) error {
|
|||||||
if s := len(scene); s > g.buffers.scene.size {
|
if s := len(scene); s > g.buffers.scene.size {
|
||||||
realloced = true
|
realloced = true
|
||||||
paddedCap := s * 11 / 10
|
paddedCap := s * 11 / 10
|
||||||
if err := g.buffers.scene.ensureCapacity(g.ctx, driver.BufferBindingShaderStorage, paddedCap); err != nil {
|
if err := g.buffers.scene.ensureCapacity(g.useCPU, g.ctx, driver.BufferBindingShaderStorage, paddedCap); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
g.buffers.scene.buffer.Upload(scene)
|
g.buffers.scene.upload(scene)
|
||||||
|
|
||||||
g.ctx.BindImageTexture(kernel4OutputUnit, dst, driver.AccessWrite, driver.TextureFormatRGBA8)
|
|
||||||
if t := g.materials.tex; t != nil {
|
|
||||||
g.ctx.BindImageTexture(kernel4AtlasUnit, t, driver.AccessRead, driver.TextureFormatRGBA8)
|
|
||||||
}
|
|
||||||
|
|
||||||
// alloc is the number of allocated bytes for static buffers.
|
// alloc is the number of allocated bytes for static buffers.
|
||||||
var alloc uint32
|
var alloc uint32
|
||||||
@@ -1027,12 +1114,14 @@ func (g *compute) render(dst driver.Texture, tileDims image.Point) error {
|
|||||||
if clearSize > g.buffers.state.size {
|
if clearSize > g.buffers.state.size {
|
||||||
realloced = true
|
realloced = true
|
||||||
paddedCap := clearSize * 11 / 10
|
paddedCap := clearSize * 11 / 10
|
||||||
if err := g.buffers.state.ensureCapacity(g.ctx, driver.BufferBindingShaderStorage, paddedCap); err != nil {
|
if err := g.buffers.state.ensureCapacity(g.useCPU, g.ctx, driver.BufferBindingShaderStorage, paddedCap); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
g.buffers.config.Upload(byteslice.Struct(g.conf))
|
confData := byteslice.Struct(g.conf)
|
||||||
|
g.buffers.config.ensureCapacity(g.useCPU, g.ctx, driver.BufferBindingShaderStorage, len(confData))
|
||||||
|
g.buffers.config.upload(confData)
|
||||||
|
|
||||||
minSize := int(unsafe.Sizeof(memoryHeader{})) + int(alloc)
|
minSize := int(unsafe.Sizeof(memoryHeader{})) + int(alloc)
|
||||||
if minSize > g.buffers.memory.size {
|
if minSize > g.buffers.memory.size {
|
||||||
@@ -1040,45 +1129,53 @@ func (g *compute) render(dst driver.Texture, tileDims image.Point) error {
|
|||||||
// Add space for dynamic GPU allocations.
|
// Add space for dynamic GPU allocations.
|
||||||
const sizeBump = 4 * 1024 * 1024
|
const sizeBump = 4 * 1024 * 1024
|
||||||
minSize += sizeBump
|
minSize += sizeBump
|
||||||
if err := g.buffers.memory.ensureCapacity(g.ctx, driver.BufferBindingShaderStorage, minSize); err != nil {
|
if err := g.buffers.memory.ensureCapacity(g.useCPU, g.ctx, driver.BufferBindingShaderStorage, minSize); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !g.useCPU {
|
||||||
|
g.ctx.BindImageTexture(kernel4OutputUnit, dst, driver.AccessWrite, driver.TextureFormatRGBA8)
|
||||||
|
if t := g.materials.tex; t != nil {
|
||||||
|
g.ctx.BindImageTexture(kernel4AtlasUnit, t, driver.AccessRead, driver.TextureFormatRGBA8)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
*g.output.descriptors.Binding2() = cpuDst
|
||||||
|
*g.output.descriptors.Binding3() = g.materials.cpuTex
|
||||||
|
}
|
||||||
|
|
||||||
for {
|
for {
|
||||||
*g.memHeader = memoryHeader{
|
*g.memHeader = memoryHeader{
|
||||||
mem_offset: alloc,
|
mem_offset: alloc,
|
||||||
}
|
}
|
||||||
g.buffers.memory.buffer.Upload(byteslice.Struct(g.memHeader))
|
g.buffers.memory.upload(byteslice.Struct(g.memHeader))
|
||||||
g.buffers.state.buffer.Upload(g.zeros(clearSize))
|
g.buffers.state.upload(g.zeros(clearSize))
|
||||||
|
|
||||||
if realloced {
|
if realloced {
|
||||||
realloced = false
|
realloced = false
|
||||||
g.bindBuffers()
|
g.bindBuffers()
|
||||||
}
|
}
|
||||||
g.ctx.MemoryBarrier()
|
g.memoryBarrier()
|
||||||
g.ctx.BindProgram(g.programs.elements)
|
g.dispatch(g.programs.elements, numPartitions, 1, 1)
|
||||||
g.ctx.DispatchCompute(numPartitions, 1, 1)
|
g.memoryBarrier()
|
||||||
g.ctx.MemoryBarrier()
|
g.dispatch(g.programs.tileAlloc, (enc.npath+wgSize-1)/wgSize, 1, 1)
|
||||||
g.ctx.BindProgram(g.programs.tileAlloc)
|
g.memoryBarrier()
|
||||||
g.ctx.DispatchCompute((enc.npath+wgSize-1)/wgSize, 1, 1)
|
g.dispatch(g.programs.pathCoarse, (enc.npathseg+31)/32, 1, 1)
|
||||||
g.ctx.MemoryBarrier()
|
g.memoryBarrier()
|
||||||
g.ctx.BindProgram(g.programs.pathCoarse)
|
g.dispatch(g.programs.backdrop, (enc.npath+wgSize-1)/wgSize, 1, 1)
|
||||||
g.ctx.DispatchCompute((enc.npathseg+31)/32, 1, 1)
|
|
||||||
g.ctx.MemoryBarrier()
|
|
||||||
g.ctx.BindProgram(g.programs.backdrop)
|
|
||||||
g.ctx.DispatchCompute((enc.npath+wgSize-1)/wgSize, 1, 1)
|
|
||||||
// No barrier needed between backdrop and binning.
|
// No barrier needed between backdrop and binning.
|
||||||
g.ctx.BindProgram(g.programs.binning)
|
g.dispatch(g.programs.binning, (enc.npath+wgSize-1)/wgSize, 1, 1)
|
||||||
g.ctx.DispatchCompute((enc.npath+wgSize-1)/wgSize, 1, 1)
|
g.memoryBarrier()
|
||||||
g.ctx.MemoryBarrier()
|
g.dispatch(g.programs.coarse, widthInBins, heightInBins, 1)
|
||||||
g.ctx.BindProgram(g.programs.coarse)
|
g.memoryBarrier()
|
||||||
g.ctx.DispatchCompute(widthInBins, heightInBins, 1)
|
g.downloadMaterials()
|
||||||
g.ctx.MemoryBarrier()
|
g.dispatch(g.programs.kernel4, tileDims.X, tileDims.Y, 1)
|
||||||
g.ctx.BindProgram(g.programs.kernel4)
|
g.memoryBarrier()
|
||||||
g.ctx.DispatchCompute(tileDims.X, tileDims.Y, 1)
|
if g.useCPU {
|
||||||
g.ctx.MemoryBarrier()
|
g.dispatcher.Sync()
|
||||||
|
}
|
||||||
|
|
||||||
if err := g.buffers.memory.buffer.Download(byteslice.Struct(g.memHeader)); err != nil {
|
if err := g.buffers.memory.download(byteslice.Struct(g.memHeader)); err != nil {
|
||||||
if err == driver.ErrContentLost {
|
if err == driver.ErrContentLost {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -1086,12 +1183,16 @@ func (g *compute) render(dst driver.Texture, tileDims image.Point) error {
|
|||||||
}
|
}
|
||||||
switch errCode := g.memHeader.mem_error; errCode {
|
switch errCode := g.memHeader.mem_error; errCode {
|
||||||
case memNoError:
|
case memNoError:
|
||||||
|
if g.useCPU {
|
||||||
|
w, h := tileDims.X*tileWidthPx, tileDims.Y*tileHeightPx
|
||||||
|
dst.Upload(image.Pt(0, 0), image.Pt(w, h), cpuDst.Data(), stride)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
case memMallocFailed:
|
case memMallocFailed:
|
||||||
// Resize memory and try again.
|
// Resize memory and try again.
|
||||||
realloced = true
|
realloced = true
|
||||||
sz := g.buffers.memory.size * 15 / 10
|
sz := g.buffers.memory.size * 15 / 10
|
||||||
if err := g.buffers.memory.ensureCapacity(g.ctx, driver.BufferBindingShaderStorage, sz); err != nil {
|
if err := g.buffers.memory.ensureCapacity(g.useCPU, g.ctx, driver.BufferBindingShaderStorage, sz); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
@@ -1101,6 +1202,48 @@ func (g *compute) render(dst driver.Texture, tileDims image.Point) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (g *compute) downloadMaterials() {
|
||||||
|
m := &g.materials
|
||||||
|
if !g.useCPU || len(m.regions) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
copyFBO := m.fbo
|
||||||
|
data := m.cpuTex.Data()
|
||||||
|
for _, r := range m.regions {
|
||||||
|
dims := r.Size()
|
||||||
|
if n := dims.X * dims.Y * 4; n > len(m.scratch) {
|
||||||
|
m.scratch = make([]byte, n)
|
||||||
|
}
|
||||||
|
copyFBO.ReadPixels(r, m.scratch)
|
||||||
|
stride := m.packer.maxDim * 4
|
||||||
|
col := r.Min.X * 4
|
||||||
|
row := stride * r.Min.Y
|
||||||
|
off := col + row
|
||||||
|
w := dims.X * 4
|
||||||
|
for y := 0; y < dims.Y; y++ {
|
||||||
|
copy(data[off:off+w], m.scratch[y*dims.X*4:])
|
||||||
|
off += stride
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *compute) memoryBarrier() {
|
||||||
|
if !g.useCPU {
|
||||||
|
g.ctx.MemoryBarrier()
|
||||||
|
} else {
|
||||||
|
g.dispatcher.Barrier()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *compute) dispatch(p computeProgram, x, y, z int) {
|
||||||
|
if !g.useCPU {
|
||||||
|
g.ctx.BindProgram(p.prog)
|
||||||
|
g.ctx.DispatchCompute(x, y, z)
|
||||||
|
} else {
|
||||||
|
g.dispatcher.Dispatch(p.progInfo, p.descriptors, x, y, z)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// zeros returns a byte slice with size bytes of zeros.
|
// zeros returns a byte slice with size bytes of zeros.
|
||||||
func (g *compute) zeros(size int) []byte {
|
func (g *compute) zeros(size int) []byte {
|
||||||
if cap(g.zeroSlice) < size {
|
if cap(g.zeroSlice) < size {
|
||||||
@@ -1109,7 +1252,7 @@ func (g *compute) zeros(size int) []byte {
|
|||||||
return g.zeroSlice[:size]
|
return g.zeroSlice[:size]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *layerAtlas) ensureSize(ctx driver.Device, size image.Point) error {
|
func (a *layerAtlas) ensureSize(useCPU bool, ctx driver.Device, size image.Point) error {
|
||||||
if a.size.X >= size.X && a.size.Y >= size.Y {
|
if a.size.X >= size.X && a.size.Y >= size.Y {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -1122,6 +1265,8 @@ func (a *layerAtlas) ensureSize(ctx driver.Device, size image.Point) error {
|
|||||||
a.image.Release()
|
a.image.Release()
|
||||||
a.image = nil
|
a.image = nil
|
||||||
}
|
}
|
||||||
|
a.cpuImage.Free()
|
||||||
|
|
||||||
img, err := ctx.NewTexture(driver.TextureFormatRGBA8, size.X, size.Y,
|
img, err := ctx.NewTexture(driver.TextureFormatRGBA8, size.X, size.Y,
|
||||||
driver.FilterNearest,
|
driver.FilterNearest,
|
||||||
driver.FilterNearest,
|
driver.FilterNearest,
|
||||||
@@ -1136,29 +1281,35 @@ func (a *layerAtlas) ensureSize(ctx driver.Device, size image.Point) error {
|
|||||||
}
|
}
|
||||||
a.fbo = fbo
|
a.fbo = fbo
|
||||||
a.image = img
|
a.image = img
|
||||||
|
if useCPU {
|
||||||
|
a.cpuImage = cpu.NewImageRGBA(size.X, size.Y)
|
||||||
|
}
|
||||||
a.size = size
|
a.size = size
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *compute) Release() {
|
func (g *compute) Release() {
|
||||||
|
if g.useCPU {
|
||||||
|
g.dispatcher.Stop()
|
||||||
|
}
|
||||||
type resource interface {
|
type resource interface {
|
||||||
Release()
|
Release()
|
||||||
}
|
}
|
||||||
res := []resource{
|
res := []resource{
|
||||||
g.programs.elements,
|
&g.programs.elements,
|
||||||
g.programs.tileAlloc,
|
&g.programs.tileAlloc,
|
||||||
g.programs.pathCoarse,
|
&g.programs.pathCoarse,
|
||||||
g.programs.backdrop,
|
&g.programs.backdrop,
|
||||||
g.programs.binning,
|
&g.programs.binning,
|
||||||
g.programs.coarse,
|
&g.programs.coarse,
|
||||||
g.programs.kernel4,
|
&g.programs.kernel4,
|
||||||
g.output.blitProg,
|
g.output.blitProg,
|
||||||
&g.output.buffer,
|
&g.output.buffer,
|
||||||
g.output.uniBuf,
|
g.output.uniBuf,
|
||||||
&g.buffers.scene,
|
&g.buffers.scene,
|
||||||
&g.buffers.state,
|
&g.buffers.state,
|
||||||
&g.buffers.memory,
|
&g.buffers.memory,
|
||||||
g.buffers.config,
|
&g.buffers.config,
|
||||||
g.images.tex,
|
g.images.tex,
|
||||||
g.materials.layout,
|
g.materials.layout,
|
||||||
g.materials.prog,
|
g.materials.prog,
|
||||||
@@ -1168,6 +1319,7 @@ func (g *compute) Release() {
|
|||||||
g.materials.uniBuf,
|
g.materials.uniBuf,
|
||||||
g.timers.t,
|
g.timers.t,
|
||||||
}
|
}
|
||||||
|
g.materials.cpuTex.Free()
|
||||||
for _, r := range res {
|
for _, r := range res {
|
||||||
if r != nil {
|
if r != nil {
|
||||||
r.Release()
|
r.Release()
|
||||||
@@ -1180,48 +1332,82 @@ func (g *compute) Release() {
|
|||||||
if a.image != nil {
|
if a.image != nil {
|
||||||
a.image.Release()
|
a.image.Release()
|
||||||
}
|
}
|
||||||
|
a.cpuImage.Free()
|
||||||
}
|
}
|
||||||
|
|
||||||
*g = compute{}
|
*g = compute{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *compute) bindBuffers() {
|
func (g *compute) bindBuffers() {
|
||||||
bindStorageBuffers(g.programs.elements, g.buffers.memory.buffer, g.buffers.config, g.buffers.scene.buffer, g.buffers.state.buffer)
|
g.bindStorageBuffers(g.programs.elements, g.buffers.memory, g.buffers.config, g.buffers.scene, g.buffers.state)
|
||||||
bindStorageBuffers(g.programs.tileAlloc, g.buffers.memory.buffer, g.buffers.config)
|
g.bindStorageBuffers(g.programs.tileAlloc, g.buffers.memory, g.buffers.config)
|
||||||
bindStorageBuffers(g.programs.pathCoarse, g.buffers.memory.buffer, g.buffers.config)
|
g.bindStorageBuffers(g.programs.pathCoarse, g.buffers.memory, g.buffers.config)
|
||||||
bindStorageBuffers(g.programs.backdrop, g.buffers.memory.buffer, g.buffers.config)
|
g.bindStorageBuffers(g.programs.backdrop, g.buffers.memory, g.buffers.config)
|
||||||
bindStorageBuffers(g.programs.binning, g.buffers.memory.buffer, g.buffers.config)
|
g.bindStorageBuffers(g.programs.binning, g.buffers.memory, g.buffers.config)
|
||||||
bindStorageBuffers(g.programs.coarse, g.buffers.memory.buffer, g.buffers.config)
|
g.bindStorageBuffers(g.programs.coarse, g.buffers.memory, g.buffers.config)
|
||||||
bindStorageBuffers(g.programs.kernel4, g.buffers.memory.buffer, g.buffers.config)
|
g.bindStorageBuffers(g.programs.kernel4, g.buffers.memory, g.buffers.config)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *computeProgram) Release() {
|
||||||
|
if p.prog != nil {
|
||||||
|
p.prog.Release()
|
||||||
|
}
|
||||||
|
*p = computeProgram{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *sizedBuffer) Release() {
|
func (b *sizedBuffer) Release() {
|
||||||
if b.buffer == nil {
|
if b.buffer == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
b.buffer.Release()
|
b.cpuBuf.Free()
|
||||||
*b = sizedBuffer{}
|
*b = sizedBuffer{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *sizedBuffer) ensureCapacity(ctx driver.Device, binding driver.BufferBinding, size int) error {
|
func (b *sizedBuffer) ensureCapacity(useCPU bool, ctx driver.Device, binding driver.BufferBinding, size int) error {
|
||||||
if b.size >= size {
|
if b.size >= size {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if b.buffer != nil {
|
if b.buffer != nil {
|
||||||
b.Release()
|
b.Release()
|
||||||
}
|
}
|
||||||
buf, err := ctx.NewBuffer(binding, size)
|
b.cpuBuf.Free()
|
||||||
if err != nil {
|
if !useCPU {
|
||||||
return err
|
buf, err := ctx.NewBuffer(binding, size)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
b.buffer = buf
|
||||||
|
} else {
|
||||||
|
b.cpuBuf = cpu.NewBuffer(size)
|
||||||
}
|
}
|
||||||
b.buffer = buf
|
|
||||||
b.size = size
|
b.size = size
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func bindStorageBuffers(prog driver.Program, buffers ...driver.Buffer) {
|
func (b *sizedBuffer) download(data []byte) error {
|
||||||
|
if b.buffer != nil {
|
||||||
|
return b.buffer.Download(data)
|
||||||
|
} else {
|
||||||
|
copy(data, b.cpuBuf.Data())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *sizedBuffer) upload(data []byte) {
|
||||||
|
if b.buffer != nil {
|
||||||
|
b.buffer.Upload(data)
|
||||||
|
} else {
|
||||||
|
copy(b.cpuBuf.Data(), data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *compute) bindStorageBuffers(prog computeProgram, buffers ...sizedBuffer) {
|
||||||
for i, buf := range buffers {
|
for i, buf := range buffers {
|
||||||
prog.SetStorageBuffer(i, buf)
|
if !g.useCPU {
|
||||||
|
prog.prog.SetStorageBuffer(i, buf.buffer)
|
||||||
|
} else {
|
||||||
|
*prog.buffers[i] = buf.cpuBuf
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+132
@@ -0,0 +1,132 @@
|
|||||||
|
// SPDX-License-Identifier: Unlicense OR MIT
|
||||||
|
|
||||||
|
package gpu
|
||||||
|
|
||||||
|
import (
|
||||||
|
"runtime"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"gioui.org/cpu"
|
||||||
|
)
|
||||||
|
|
||||||
|
const supportsCPUCompute = runtime.GOARCH == "amd64" || runtime.GOARCH == "arm64" || runtime.GOARCH == "arm"
|
||||||
|
|
||||||
|
// This file contains code specific to running compute shaders on the CPU.
|
||||||
|
|
||||||
|
// dispatcher dispatches CPU compute programs across multiple goroutines.
|
||||||
|
type dispatcher struct {
|
||||||
|
// done is notified when a worker completes its work slice.
|
||||||
|
done chan struct{}
|
||||||
|
// work receives work slice indices. It is closed when the dispatcher is released.
|
||||||
|
work chan work
|
||||||
|
// dispatch receives compute jobs, which is then split among workers.
|
||||||
|
dispatch chan dispatch
|
||||||
|
// sync receives notification when a Sync completes.
|
||||||
|
sync chan struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type work struct {
|
||||||
|
ctx *cpu.DispatchContext
|
||||||
|
index int
|
||||||
|
}
|
||||||
|
|
||||||
|
type dispatch struct {
|
||||||
|
_type jobType
|
||||||
|
program *cpu.ProgramInfo
|
||||||
|
descSet unsafe.Pointer
|
||||||
|
x, y, z int
|
||||||
|
}
|
||||||
|
|
||||||
|
type jobType uint8
|
||||||
|
|
||||||
|
const (
|
||||||
|
jobDispatch jobType = iota
|
||||||
|
jobBarrier
|
||||||
|
jobSync
|
||||||
|
)
|
||||||
|
|
||||||
|
func newDispatcher(workers int) *dispatcher {
|
||||||
|
d := &dispatcher{
|
||||||
|
work: make(chan work, workers),
|
||||||
|
done: make(chan struct{}, workers),
|
||||||
|
// Leave some room to avoid blocking calls to Dispatch.
|
||||||
|
dispatch: make(chan dispatch, 20),
|
||||||
|
sync: make(chan struct{}),
|
||||||
|
}
|
||||||
|
for i := 0; i < workers; i++ {
|
||||||
|
go d.worker()
|
||||||
|
}
|
||||||
|
go d.dispatcher()
|
||||||
|
return d
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *dispatcher) dispatcher() {
|
||||||
|
defer close(d.work)
|
||||||
|
var free []*cpu.DispatchContext
|
||||||
|
defer func() {
|
||||||
|
for _, ctx := range free {
|
||||||
|
ctx.Free()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
var used []*cpu.DispatchContext
|
||||||
|
for job := range d.dispatch {
|
||||||
|
switch job._type {
|
||||||
|
case jobDispatch:
|
||||||
|
if len(free) == 0 {
|
||||||
|
free = append(free, cpu.NewDispatchContext())
|
||||||
|
}
|
||||||
|
ctx := free[len(free)-1]
|
||||||
|
free = free[:len(free)-1]
|
||||||
|
used = append(used, ctx)
|
||||||
|
ctx.Prepare(cap(d.work), job.program, job.descSet, job.x, job.y, job.z)
|
||||||
|
for i := 0; i < cap(d.work); i++ {
|
||||||
|
d.work <- work{
|
||||||
|
ctx: ctx,
|
||||||
|
index: i,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case jobBarrier:
|
||||||
|
// Wait for all outstanding dispatches to complete.
|
||||||
|
for i := 0; i < len(used)*cap(d.work); i++ {
|
||||||
|
<-d.done
|
||||||
|
}
|
||||||
|
free = append(free, used...)
|
||||||
|
used = used[:0]
|
||||||
|
case jobSync:
|
||||||
|
d.sync <- struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *dispatcher) worker() {
|
||||||
|
thread := cpu.NewThreadContext()
|
||||||
|
defer thread.Free()
|
||||||
|
for w := range d.work {
|
||||||
|
w.ctx.Dispatch(w.index, thread)
|
||||||
|
d.done <- struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *dispatcher) Barrier() {
|
||||||
|
d.dispatch <- dispatch{_type: jobBarrier}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *dispatcher) Sync() {
|
||||||
|
d.dispatch <- dispatch{_type: jobSync}
|
||||||
|
<-d.sync
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *dispatcher) Dispatch(program *cpu.ProgramInfo, descSet unsafe.Pointer, x, y, z int) {
|
||||||
|
d.dispatch <- dispatch{
|
||||||
|
_type: jobDispatch,
|
||||||
|
program: program,
|
||||||
|
descSet: descSet,
|
||||||
|
x: x,
|
||||||
|
y: y,
|
||||||
|
z: z,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *dispatcher) Stop() {
|
||||||
|
close(d.dispatch)
|
||||||
|
}
|
||||||
+1
-5
@@ -9,7 +9,6 @@ package gpu
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"image"
|
"image"
|
||||||
"image/color"
|
"image/color"
|
||||||
@@ -364,11 +363,8 @@ func New(api API) (GPU, error) {
|
|||||||
switch {
|
switch {
|
||||||
case !forceCompute && feats.Has(driver.FeatureFloatRenderTargets):
|
case !forceCompute && feats.Has(driver.FeatureFloatRenderTargets):
|
||||||
return newGPU(d)
|
return newGPU(d)
|
||||||
case feats.Has(driver.FeatureCompute):
|
|
||||||
return newCompute(d)
|
|
||||||
default:
|
|
||||||
return nil, errors.New("gpu: no support for float render targets nor compute")
|
|
||||||
}
|
}
|
||||||
|
return newCompute(d)
|
||||||
}
|
}
|
||||||
|
|
||||||
func newGPU(ctx driver.Device) (*gpu, error) {
|
func newGPU(ctx driver.Device) (*gpu, error) {
|
||||||
|
|||||||
Reference in New Issue
Block a user