chore(v2): vendor dependencies for offline/China builds

go mod vendor pins onnxruntime_go v1.12.1, Gio and the rest into v2/vendor
so go run/build work without hitting proxy.golang.org (blocked/slow in
China). Verified: CGO_ENABLED=1 go build -mod=vendor ./internal/spike and
GOOS=windows go build -mod=vendor ./internal/ui both pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
ila
2026-07-23 16:35:01 +08:00
co-authored by Claude Opus 4.8
parent 97c1c4a974
commit f58728cddd
972 changed files with 597802 additions and 0 deletions
+40
View File
@@ -0,0 +1,40 @@
// SPDX-License-Identifier: Unlicense OR MIT
package gpu
import "gioui.org/gpu/internal/driver"
// An API carries the necessary GPU API specific resources to create a Device.
// There is an API type for each supported GPU API such as OpenGL and Direct3D.
type API = driver.API
// A RenderTarget denotes the destination framebuffer for a frame.
type RenderTarget = driver.RenderTarget
// OpenGLRenderTarget is a render target suitable for the OpenGL backend.
type OpenGLRenderTarget = driver.OpenGLRenderTarget
// Direct3D11RenderTarget is a render target suitable for the Direct3D 11 backend.
type Direct3D11RenderTarget = driver.Direct3D11RenderTarget
// MetalRenderTarget is a render target suitable for the Metal backend.
type MetalRenderTarget = driver.MetalRenderTarget
// VulkanRenderTarget is a render target suitable for the Vulkan backend.
type VulkanRenderTarget = driver.VulkanRenderTarget
// OpenGL denotes the OpenGL or OpenGL ES API.
type OpenGL = driver.OpenGL
// Direct3D11 denotes the Direct3D API.
type Direct3D11 = driver.Direct3D11
// Metal denotes the Apple Metal API.
type Metal = driver.Metal
// Vulkan denotes the Vulkan API.
type Vulkan = driver.Vulkan
// ErrDeviceLost is returned from GPU operations when the underlying GPU device
// is lost and should be recreated.
var ErrDeviceLost = driver.ErrDeviceLost
+153
View File
@@ -0,0 +1,153 @@
// SPDX-License-Identifier: Unlicense OR MIT
package gpu
import (
"fmt"
"gioui.org/internal/f32"
)
type textureCacheKey struct {
filter byte
handle any
}
type textureCache struct {
res map[textureCacheKey]resourceCacheValue
}
type resourceCacheValue struct {
used bool
resource resource
}
// opCache is like a resourceCache but using concrete types and a
// freelist instead of two maps to avoid runtime.mapaccess2 calls
// since benchmarking showed them as a bottleneck.
type opCache struct {
// store the index + 1 in cache this key is stored in
index map[opKey]int
// list of indexes in cache that are free and can be used
freelist []int
cache []opCacheValue
}
type opCacheValue struct {
data pathData
bounds f32.Rectangle
// the fields below are handled by opCache
key opKey
keep bool
}
func newTextureCache() *textureCache {
return &textureCache{
res: make(map[textureCacheKey]resourceCacheValue),
}
}
func (r *textureCache) get(key textureCacheKey) (resource, bool) {
v, exists := r.res[key]
if !exists {
return nil, false
}
if !v.used {
v.used = true
r.res[key] = v
}
return v.resource, exists
}
func (r *textureCache) put(key textureCacheKey, val resource) {
v, exists := r.res[key]
if exists && v.used {
panic(fmt.Errorf("key exists, %v", key))
}
v.used = true
v.resource = val
r.res[key] = v
}
func (r *textureCache) frame() {
for k, v := range r.res {
if v.used {
v.used = false
r.res[k] = v
} else {
delete(r.res, k)
v.resource.release()
}
}
}
func (r *textureCache) release() {
for _, v := range r.res {
v.resource.release()
}
r.res = nil
}
func newOpCache() *opCache {
return &opCache{
index: make(map[opKey]int),
freelist: make([]int, 0),
cache: make([]opCacheValue, 0),
}
}
func (r *opCache) get(key opKey) (o opCacheValue, exist bool) {
v := r.index[key]
if v == 0 {
return
}
r.cache[v-1].keep = true
return r.cache[v-1], true
}
func (r *opCache) put(key opKey, val opCacheValue) {
v := r.index[key]
val.keep = true
val.key = key
if v == 0 {
// not in cache
i := len(r.cache)
if len(r.freelist) > 0 {
i = r.freelist[len(r.freelist)-1]
r.freelist = r.freelist[:len(r.freelist)-1]
r.cache[i] = val
} else {
r.cache = append(r.cache, val)
}
r.index[key] = i + 1
} else {
r.cache[v-1] = val
}
}
func (r *opCache) frame() {
r.freelist = r.freelist[:0]
for i, v := range r.cache {
r.cache[i].keep = false
if v.keep {
continue
}
if v.data.data != nil {
v.data.release()
r.cache[i].data.data = nil
}
delete(r.index, v.key)
r.freelist = append(r.freelist, i)
}
}
func (r *opCache) release() {
for i := range r.cache {
r.cache[i].keep = false
}
r.frame()
r.index = nil
r.freelist = nil
r.cache = nil
}
+146
View File
@@ -0,0 +1,146 @@
package gpu
import (
"encoding/binary"
"math"
"gioui.org/internal/f32"
"gioui.org/internal/stroke"
)
type quadSplitter struct {
bounds f32.Rectangle
contour uint32
d *drawOps
// scratch space used by calls to stroke.SplitCubic
scratch []stroke.QuadSegment
}
func encodeQuadTo(data []byte, meta uint32, from, ctrl, to f32.Point) {
// inlined code:
// encodeVertex(data, meta, 1, -1, from, ctrl, to)
// encodeVertex(data[vertStride:], meta, 1, 1, from, ctrl, to)
// encodeVertex(data[vertStride*2:], meta, -1, -1, from, ctrl, to)
// encodeVertex(data[vertStride*3:], meta, -1, 1, from, ctrl, to)
// this code needs to stay in sync with `vertex.encode`.
bo := binary.LittleEndian
data = data[:vertStride*4]
// encode the main template
bo.PutUint32(data[4:8], meta)
bo.PutUint32(data[8:12], math.Float32bits(from.X))
bo.PutUint32(data[12:16], math.Float32bits(from.Y))
bo.PutUint32(data[16:20], math.Float32bits(ctrl.X))
bo.PutUint32(data[20:24], math.Float32bits(ctrl.Y))
bo.PutUint32(data[24:28], math.Float32bits(to.X))
bo.PutUint32(data[28:32], math.Float32bits(to.Y))
copy(data[vertStride*1:vertStride*2], data[vertStride*0:vertStride*1])
copy(data[vertStride*2:vertStride*3], data[vertStride*0:vertStride*1])
copy(data[vertStride*3:vertStride*4], data[vertStride*0:vertStride*1])
bo.PutUint32(data[vertStride*0:vertStride*0+4], math.Float32bits(nwCorner))
bo.PutUint32(data[vertStride*1:vertStride*1+4], math.Float32bits(neCorner))
bo.PutUint32(data[vertStride*2:vertStride*2+4], math.Float32bits(swCorner))
bo.PutUint32(data[vertStride*3:vertStride*3+4], math.Float32bits(seCorner))
}
const (
nwCorner = 1*0.5 + 0*0.25
neCorner = 1*0.5 + 1*0.25
swCorner = 0*0.5 + 0*0.25
seCorner = 0*0.5 + 1*0.25
)
func encodeVertex(data []byte, meta uint32, cornerx, cornery int16, from, ctrl, to f32.Point) {
var corner float32
if cornerx == 1 {
corner += .5
}
if cornery == 1 {
corner += .25
}
v := vertex{
Corner: corner,
FromX: from.X,
FromY: from.Y,
CtrlX: ctrl.X,
CtrlY: ctrl.Y,
ToX: to.X,
ToY: to.Y,
}
v.encode(data, meta)
}
func (qs *quadSplitter) encodeQuadTo(from, ctrl, to f32.Point) {
data := qs.d.writeVertCache(vertStride * 4)
encodeQuadTo(data, qs.contour, from, ctrl, to)
}
func (qs *quadSplitter) splitAndEncode(quad stroke.QuadSegment) {
cbnd := f32.Rectangle{
Min: quad.From,
Max: quad.To,
}.Canon()
from, ctrl, to := quad.From, quad.Ctrl, quad.To
// If the curve contain areas where a vertical line
// intersects it twice, split the curve in two x monotone
// lower and upper curves. The stencil fragment program
// expects only one intersection per curve.
// Find the t where the derivative in x is 0.
v0 := ctrl.Sub(from)
v1 := to.Sub(ctrl)
d := v0.X - v1.X
// t = v0 / d. Split if t is in ]0;1[.
if v0.X > 0 && d > v0.X || v0.X < 0 && d < v0.X {
t := v0.X / d
ctrl0 := from.Mul(1 - t).Add(ctrl.Mul(t))
ctrl1 := ctrl.Mul(1 - t).Add(to.Mul(t))
mid := ctrl0.Mul(1 - t).Add(ctrl1.Mul(t))
qs.encodeQuadTo(from, ctrl0, mid)
qs.encodeQuadTo(mid, ctrl1, to)
if mid.X > cbnd.Max.X {
cbnd.Max.X = mid.X
}
if mid.X < cbnd.Min.X {
cbnd.Min.X = mid.X
}
} else {
qs.encodeQuadTo(from, ctrl, to)
}
// Find the y extremum, if any.
d = v0.Y - v1.Y
if v0.Y > 0 && d > v0.Y || v0.Y < 0 && d < v0.Y {
t := v0.Y / d
y := (1-t)*(1-t)*from.Y + 2*(1-t)*t*ctrl.Y + t*t*to.Y
if y > cbnd.Max.Y {
cbnd.Max.Y = y
}
if y < cbnd.Min.Y {
cbnd.Min.Y = y
}
}
qs.bounds = unionRect(qs.bounds, cbnd)
}
// Union is like f32.Rectangle.Union but ignores empty rectangles.
func unionRect(r, s f32.Rectangle) f32.Rectangle {
if r.Min.X > s.Min.X {
r.Min.X = s.Min.X
}
if r.Min.Y > s.Min.Y {
r.Min.Y = s.Min.Y
}
if r.Max.X < s.Max.X {
r.Max.X = s.Max.X
}
if r.Max.Y < s.Max.Y {
r.Max.Y = s.Max.Y
}
return r
}
+1603
View File
File diff suppressed because it is too large Load Diff
+5
View File
@@ -0,0 +1,5 @@
// SPDX-License-Identifier: Unlicense OR MIT
// This file exists so this package builds on non-Windows platforms.
package d3d11
+871
View File
@@ -0,0 +1,871 @@
// SPDX-License-Identifier: Unlicense OR MIT
package d3d11
import (
"errors"
"fmt"
"image"
"math"
"math/bits"
"unsafe"
"golang.org/x/sys/windows"
"gioui.org/gpu/internal/driver"
"gioui.org/internal/d3d11"
"gioui.org/shader"
)
type Backend struct {
dev *d3d11.Device
ctx *d3d11.DeviceContext
// Temporary storage to avoid garbage.
clearColor [4]float32
viewport d3d11.VIEWPORT
pipeline *Pipeline
vert struct {
buffer *Buffer
offset int
}
program *Program
caps driver.Caps
floatFormat uint32
}
type Pipeline struct {
vert *d3d11.VertexShader
frag *d3d11.PixelShader
layout *d3d11.InputLayout
blend *d3d11.BlendState
stride int
topology driver.Topology
}
type Texture struct {
backend *Backend
format uint32
bindings driver.BufferBinding
tex *d3d11.Texture2D
sampler *d3d11.SamplerState
resView *d3d11.ShaderResourceView
uaView *d3d11.UnorderedAccessView
renderTarget *d3d11.RenderTargetView
width int
height int
mipmap bool
foreign bool
}
type VertexShader struct {
backend *Backend
shader *d3d11.VertexShader
src shader.Sources
}
type FragmentShader struct {
backend *Backend
shader *d3d11.PixelShader
}
type Program struct {
backend *Backend
shader *d3d11.ComputeShader
}
type Buffer struct {
backend *Backend
bind uint32
buf *d3d11.Buffer
resView *d3d11.ShaderResourceView
uaView *d3d11.UnorderedAccessView
size int
immutable bool
}
func init() {
driver.NewDirect3D11Device = newDirect3D11Device
}
func detectFloatFormat(dev *d3d11.Device) (uint32, bool) {
formats := []uint32{
d3d11.DXGI_FORMAT_R16_FLOAT,
d3d11.DXGI_FORMAT_R32_FLOAT,
d3d11.DXGI_FORMAT_R16G16_FLOAT,
d3d11.DXGI_FORMAT_R32G32_FLOAT,
// These last two are really wasteful, but c'est la vie.
d3d11.DXGI_FORMAT_R16G16B16A16_FLOAT,
d3d11.DXGI_FORMAT_R32G32B32A32_FLOAT,
}
for _, format := range formats {
need := uint32(d3d11.FORMAT_SUPPORT_TEXTURE2D | d3d11.FORMAT_SUPPORT_RENDER_TARGET)
if support, _ := dev.CheckFormatSupport(format); support&need == need {
return format, true
}
}
return 0, false
}
func newDirect3D11Device(api driver.Direct3D11) (driver.Device, error) {
dev := (*d3d11.Device)(api.Device)
b := &Backend{
dev: dev,
ctx: dev.GetImmediateContext(),
caps: driver.Caps{
MaxTextureSize: 2048, // 9.1 maximum
Features: driver.FeatureSRGB,
},
}
featLvl := dev.GetFeatureLevel()
switch {
case featLvl < d3d11.FEATURE_LEVEL_9_1:
d3d11.IUnknownRelease(unsafe.Pointer(dev), dev.Vtbl.Release)
d3d11.IUnknownRelease(unsafe.Pointer(b.ctx), b.ctx.Vtbl.Release)
return nil, fmt.Errorf("d3d11: feature level too low: %d", featLvl)
case featLvl >= d3d11.FEATURE_LEVEL_11_0:
b.caps.MaxTextureSize = 16384
b.caps.Features |= driver.FeatureCompute
case featLvl >= d3d11.FEATURE_LEVEL_9_3:
b.caps.MaxTextureSize = 4096
}
if fmt, ok := detectFloatFormat(dev); ok {
b.floatFormat = fmt
b.caps.Features |= driver.FeatureFloatRenderTargets
}
// Disable backface culling to match OpenGL.
state, err := dev.CreateRasterizerState(&d3d11.RASTERIZER_DESC{
CullMode: d3d11.CULL_NONE,
FillMode: d3d11.FILL_SOLID,
})
if err != nil {
return nil, err
}
defer d3d11.IUnknownRelease(unsafe.Pointer(state), state.Vtbl.Release)
b.ctx.RSSetState(state)
return b, nil
}
func (b *Backend) BeginFrame(target driver.RenderTarget, clear bool, viewport image.Point) driver.Texture {
var renderTarget *d3d11.RenderTargetView
if target != nil {
switch t := target.(type) {
case driver.Direct3D11RenderTarget:
renderTarget = (*d3d11.RenderTargetView)(t.RenderTarget)
case *Texture:
renderTarget = t.renderTarget
default:
panic(fmt.Errorf("d3d11: invalid render target type: %T", target))
}
}
b.ctx.OMSetRenderTargets(renderTarget, nil)
return &Texture{backend: b, renderTarget: renderTarget, foreign: true}
}
func (b *Backend) CopyTexture(dstTex driver.Texture, dstOrigin image.Point, srcTex driver.Texture, srcRect image.Rectangle) {
dst := (*d3d11.Resource)(unsafe.Pointer(dstTex.(*Texture).tex))
src := (*d3d11.Resource)(srcTex.(*Texture).tex)
b.ctx.CopySubresourceRegion(
dst,
0, // Destination subresource.
uint32(dstOrigin.X), uint32(dstOrigin.Y), 0, // Destination coordinates (x, y, z).
src,
0, // Source subresource.
&d3d11.BOX{
Left: uint32(srcRect.Min.X),
Top: uint32(srcRect.Min.Y),
Right: uint32(srcRect.Max.X),
Bottom: uint32(srcRect.Max.Y),
Front: 0,
Back: 1,
},
)
}
func (b *Backend) EndFrame() {
}
func (b *Backend) Caps() driver.Caps {
return b.caps
}
func (b *Backend) NewTimer() driver.Timer {
panic("timers not supported")
}
func (b *Backend) IsTimeContinuous() bool {
panic("timers not supported")
}
func (b *Backend) Release() {
d3d11.IUnknownRelease(unsafe.Pointer(b.ctx), b.ctx.Vtbl.Release)
*b = Backend{}
}
func (b *Backend) NewTexture(format driver.TextureFormat, width, height int, minFilter, magFilter driver.TextureFilter, bindings driver.BufferBinding) (driver.Texture, error) {
var d3dfmt uint32
switch format {
case driver.TextureFormatFloat:
d3dfmt = b.floatFormat
case driver.TextureFormatSRGBA:
d3dfmt = d3d11.DXGI_FORMAT_R8G8B8A8_UNORM_SRGB
case driver.TextureFormatRGBA8:
d3dfmt = d3d11.DXGI_FORMAT_R8G8B8A8_UNORM
default:
return nil, fmt.Errorf("unsupported texture format %d", format)
}
bindFlags := convBufferBinding(bindings)
miscFlags := uint32(0)
mipmap := minFilter == driver.FilterLinearMipmapLinear
nmipmaps := 1
if mipmap {
// Flags required by ID3D11DeviceContext::GenerateMips.
bindFlags |= d3d11.BIND_SHADER_RESOURCE | d3d11.BIND_RENDER_TARGET
miscFlags |= d3d11.RESOURCE_MISC_GENERATE_MIPS
dim := max(height, width)
log2 := 32 - bits.LeadingZeros32(uint32(dim)) - 1
nmipmaps = log2 + 1
}
tex, err := b.dev.CreateTexture2D(&d3d11.TEXTURE2D_DESC{
Width: uint32(width),
Height: uint32(height),
MipLevels: uint32(nmipmaps),
ArraySize: 1,
Format: d3dfmt,
SampleDesc: d3d11.DXGI_SAMPLE_DESC{
Count: 1,
Quality: 0,
},
BindFlags: bindFlags,
MiscFlags: miscFlags,
})
if err != nil {
return nil, err
}
var (
sampler *d3d11.SamplerState
resView *d3d11.ShaderResourceView
uaView *d3d11.UnorderedAccessView
fbo *d3d11.RenderTargetView
)
if bindings&driver.BufferBindingTexture != 0 {
var filter uint32
switch {
case minFilter == driver.FilterNearest && magFilter == driver.FilterNearest:
filter = d3d11.FILTER_MIN_MAG_MIP_POINT
case minFilter == driver.FilterLinear && magFilter == driver.FilterLinear:
filter = d3d11.FILTER_MIN_MAG_LINEAR_MIP_POINT
case minFilter == driver.FilterLinearMipmapLinear && magFilter == driver.FilterLinear:
filter = d3d11.FILTER_MIN_MAG_MIP_LINEAR
default:
d3d11.IUnknownRelease(unsafe.Pointer(tex), tex.Vtbl.Release)
return nil, fmt.Errorf("unsupported texture filter combination %d, %d", minFilter, magFilter)
}
var err error
sampler, err = b.dev.CreateSamplerState(&d3d11.SAMPLER_DESC{
Filter: filter,
AddressU: d3d11.TEXTURE_ADDRESS_CLAMP,
AddressV: d3d11.TEXTURE_ADDRESS_CLAMP,
AddressW: d3d11.TEXTURE_ADDRESS_CLAMP,
MaxAnisotropy: 1,
MinLOD: -math.MaxFloat32,
MaxLOD: math.MaxFloat32,
})
if err != nil {
d3d11.IUnknownRelease(unsafe.Pointer(tex), tex.Vtbl.Release)
return nil, err
}
resView, err = b.dev.CreateShaderResourceView(
(*d3d11.Resource)(unsafe.Pointer(tex)),
unsafe.Pointer(&d3d11.SHADER_RESOURCE_VIEW_DESC_TEX2D{
SHADER_RESOURCE_VIEW_DESC: d3d11.SHADER_RESOURCE_VIEW_DESC{
Format: d3dfmt,
ViewDimension: d3d11.SRV_DIMENSION_TEXTURE2D,
},
Texture2D: d3d11.TEX2D_SRV{
MostDetailedMip: 0,
MipLevels: ^uint32(0),
},
}),
)
if err != nil {
d3d11.IUnknownRelease(unsafe.Pointer(tex), tex.Vtbl.Release)
d3d11.IUnknownRelease(unsafe.Pointer(sampler), sampler.Vtbl.Release)
return nil, err
}
}
if bindings&driver.BufferBindingShaderStorageWrite != 0 {
uaView, err = b.dev.CreateUnorderedAccessView(
(*d3d11.Resource)(unsafe.Pointer(tex)),
unsafe.Pointer(&d3d11.UNORDERED_ACCESS_VIEW_DESC_TEX2D{
UNORDERED_ACCESS_VIEW_DESC: d3d11.UNORDERED_ACCESS_VIEW_DESC{
Format: d3dfmt,
ViewDimension: d3d11.UAV_DIMENSION_TEXTURE2D,
},
Texture2D: d3d11.TEX2D_UAV{
MipSlice: 0,
},
}),
)
if err != nil {
if sampler != nil {
d3d11.IUnknownRelease(unsafe.Pointer(sampler), sampler.Vtbl.Release)
}
if resView != nil {
d3d11.IUnknownRelease(unsafe.Pointer(resView), resView.Vtbl.Release)
}
d3d11.IUnknownRelease(unsafe.Pointer(tex), tex.Vtbl.Release)
return nil, err
}
}
if bindings&driver.BufferBindingFramebuffer != 0 {
resource := (*d3d11.Resource)(unsafe.Pointer(tex))
fbo, err = b.dev.CreateRenderTargetView(resource)
if err != nil {
if uaView != nil {
d3d11.IUnknownRelease(unsafe.Pointer(uaView), uaView.Vtbl.Release)
}
if sampler != nil {
d3d11.IUnknownRelease(unsafe.Pointer(sampler), sampler.Vtbl.Release)
}
if resView != nil {
d3d11.IUnknownRelease(unsafe.Pointer(resView), resView.Vtbl.Release)
}
d3d11.IUnknownRelease(unsafe.Pointer(tex), tex.Vtbl.Release)
return nil, err
}
}
return &Texture{backend: b, format: d3dfmt, tex: tex, sampler: sampler, resView: resView, uaView: uaView, renderTarget: fbo, bindings: bindings, width: width, height: height, mipmap: mipmap}, nil
}
func (b *Backend) newInputLayout(vertexShader shader.Sources, layout []driver.InputDesc) (*d3d11.InputLayout, error) {
if len(vertexShader.Inputs) != len(layout) {
return nil, fmt.Errorf("NewInputLayout: got %d inputs, expected %d", len(layout), len(vertexShader.Inputs))
}
descs := make([]d3d11.INPUT_ELEMENT_DESC, len(layout))
for i, l := range layout {
inp := vertexShader.Inputs[i]
cname, err := windows.BytePtrFromString(inp.Semantic)
if err != nil {
return nil, err
}
var format uint32
switch l.Type {
case shader.DataTypeFloat:
switch l.Size {
case 1:
format = d3d11.DXGI_FORMAT_R32_FLOAT
case 2:
format = d3d11.DXGI_FORMAT_R32G32_FLOAT
case 3:
format = d3d11.DXGI_FORMAT_R32G32B32_FLOAT
case 4:
format = d3d11.DXGI_FORMAT_R32G32B32A32_FLOAT
default:
panic("unsupported data size")
}
case shader.DataTypeShort:
switch l.Size {
case 1:
format = d3d11.DXGI_FORMAT_R16_SINT
case 2:
format = d3d11.DXGI_FORMAT_R16G16_SINT
default:
panic("unsupported data size")
}
default:
panic("unsupported data type")
}
descs[i] = d3d11.INPUT_ELEMENT_DESC{
SemanticName: cname,
SemanticIndex: uint32(inp.SemanticIndex),
Format: format,
AlignedByteOffset: uint32(l.Offset),
}
}
return b.dev.CreateInputLayout(descs, []byte(vertexShader.DXBC))
}
func (b *Backend) NewBuffer(typ driver.BufferBinding, size int) (driver.Buffer, error) {
return b.newBuffer(typ, size, nil, false)
}
func (b *Backend) NewImmutableBuffer(typ driver.BufferBinding, data []byte) (driver.Buffer, error) {
return b.newBuffer(typ, len(data), data, true)
}
func (b *Backend) newBuffer(typ driver.BufferBinding, size int, data []byte, immutable bool) (*Buffer, error) {
if typ&driver.BufferBindingUniforms != 0 {
if typ != driver.BufferBindingUniforms {
return nil, errors.New("uniform buffers cannot have other bindings")
}
if size%16 != 0 {
return nil, fmt.Errorf("constant buffer size is %d, expected a multiple of 16", size)
}
}
bind := convBufferBinding(typ)
var usage, miscFlags, cpuFlags uint32
if immutable {
usage = d3d11.USAGE_IMMUTABLE
}
if typ&driver.BufferBindingShaderStorageWrite != 0 {
cpuFlags = d3d11.CPU_ACCESS_READ
}
if typ&(driver.BufferBindingShaderStorageRead|driver.BufferBindingShaderStorageWrite) != 0 {
miscFlags |= d3d11.RESOURCE_MISC_BUFFER_ALLOW_RAW_VIEWS
}
buf, err := b.dev.CreateBuffer(&d3d11.BUFFER_DESC{
ByteWidth: uint32(size),
Usage: usage,
BindFlags: bind,
CPUAccessFlags: cpuFlags,
MiscFlags: miscFlags,
}, data)
if err != nil {
return nil, err
}
var (
resView *d3d11.ShaderResourceView
uaView *d3d11.UnorderedAccessView
)
if typ&driver.BufferBindingShaderStorageWrite != 0 {
uaView, err = b.dev.CreateUnorderedAccessView(
(*d3d11.Resource)(unsafe.Pointer(buf)),
unsafe.Pointer(&d3d11.UNORDERED_ACCESS_VIEW_DESC_BUFFER{
UNORDERED_ACCESS_VIEW_DESC: d3d11.UNORDERED_ACCESS_VIEW_DESC{
Format: d3d11.DXGI_FORMAT_R32_TYPELESS,
ViewDimension: d3d11.UAV_DIMENSION_BUFFER,
},
Buffer: d3d11.BUFFER_UAV{
FirstElement: 0,
NumElements: uint32(size / 4),
Flags: d3d11.BUFFER_UAV_FLAG_RAW,
},
}),
)
if err != nil {
d3d11.IUnknownRelease(unsafe.Pointer(buf), buf.Vtbl.Release)
return nil, err
}
} else if typ&driver.BufferBindingShaderStorageRead != 0 {
resView, err = b.dev.CreateShaderResourceView(
(*d3d11.Resource)(unsafe.Pointer(buf)),
unsafe.Pointer(&d3d11.SHADER_RESOURCE_VIEW_DESC_BUFFEREX{
SHADER_RESOURCE_VIEW_DESC: d3d11.SHADER_RESOURCE_VIEW_DESC{
Format: d3d11.DXGI_FORMAT_R32_TYPELESS,
ViewDimension: d3d11.SRV_DIMENSION_BUFFEREX,
},
Buffer: d3d11.BUFFEREX_SRV{
FirstElement: 0,
NumElements: uint32(size / 4),
Flags: d3d11.BUFFEREX_SRV_FLAG_RAW,
},
}),
)
if err != nil {
d3d11.IUnknownRelease(unsafe.Pointer(buf), buf.Vtbl.Release)
return nil, err
}
}
return &Buffer{backend: b, buf: buf, bind: bind, size: size, resView: resView, uaView: uaView, immutable: immutable}, nil
}
func (b *Backend) NewComputeProgram(shader shader.Sources) (driver.Program, error) {
cs, err := b.dev.CreateComputeShader([]byte(shader.DXBC))
if err != nil {
return nil, err
}
return &Program{backend: b, shader: cs}, nil
}
func (b *Backend) NewPipeline(desc driver.PipelineDesc) (driver.Pipeline, error) {
vsh := desc.VertexShader.(*VertexShader)
fsh := desc.FragmentShader.(*FragmentShader)
blend, err := b.newBlendState(desc.BlendDesc)
if err != nil {
return nil, err
}
var layout *d3d11.InputLayout
if l := desc.VertexLayout; l.Stride > 0 {
var err error
layout, err = b.newInputLayout(vsh.src, l.Inputs)
if err != nil {
d3d11.IUnknownRelease(unsafe.Pointer(blend), blend.Vtbl.AddRef)
return nil, err
}
}
// Retain shaders.
vshRef := vsh.shader
fshRef := fsh.shader
d3d11.IUnknownAddRef(unsafe.Pointer(vshRef), vshRef.Vtbl.AddRef)
d3d11.IUnknownAddRef(unsafe.Pointer(fshRef), fshRef.Vtbl.AddRef)
return &Pipeline{
vert: vshRef,
frag: fshRef,
layout: layout,
stride: desc.VertexLayout.Stride,
blend: blend,
topology: desc.Topology,
}, nil
}
func (b *Backend) newBlendState(desc driver.BlendDesc) (*d3d11.BlendState, error) {
var d3ddesc d3d11.BLEND_DESC
t0 := &d3ddesc.RenderTarget[0]
t0.RenderTargetWriteMask = d3d11.COLOR_WRITE_ENABLE_ALL
t0.BlendOp = d3d11.BLEND_OP_ADD
t0.BlendOpAlpha = d3d11.BLEND_OP_ADD
if desc.Enable {
t0.BlendEnable = 1
}
scol, salpha := toBlendFactor(desc.SrcFactor)
dcol, dalpha := toBlendFactor(desc.DstFactor)
t0.SrcBlend = scol
t0.SrcBlendAlpha = salpha
t0.DestBlend = dcol
t0.DestBlendAlpha = dalpha
return b.dev.CreateBlendState(&d3ddesc)
}
func (b *Backend) NewVertexShader(src shader.Sources) (driver.VertexShader, error) {
vs, err := b.dev.CreateVertexShader([]byte(src.DXBC))
if err != nil {
return nil, err
}
return &VertexShader{b, vs, src}, nil
}
func (b *Backend) NewFragmentShader(src shader.Sources) (driver.FragmentShader, error) {
fs, err := b.dev.CreatePixelShader([]byte(src.DXBC))
if err != nil {
return nil, err
}
return &FragmentShader{b, fs}, nil
}
func (b *Backend) Viewport(x, y, width, height int) {
b.viewport = d3d11.VIEWPORT{
TopLeftX: float32(x),
TopLeftY: float32(y),
Width: float32(width),
Height: float32(height),
MinDepth: 0.0,
MaxDepth: 1.0,
}
b.ctx.RSSetViewports(&b.viewport)
}
func (b *Backend) DrawArrays(off, count int) {
b.prepareDraw()
b.ctx.Draw(uint32(count), uint32(off))
}
func (b *Backend) DrawElements(off, count int) {
b.prepareDraw()
b.ctx.DrawIndexed(uint32(count), uint32(off), 0)
}
func (b *Backend) prepareDraw() {
p := b.pipeline
if p == nil {
return
}
b.ctx.VSSetShader(p.vert)
b.ctx.PSSetShader(p.frag)
b.ctx.IASetInputLayout(p.layout)
b.ctx.OMSetBlendState(p.blend, nil, 0xffffffff)
if b.vert.buffer != nil {
b.ctx.IASetVertexBuffers(b.vert.buffer.buf, uint32(p.stride), uint32(b.vert.offset))
}
var topology uint32
switch p.topology {
case driver.TopologyTriangles:
topology = d3d11.PRIMITIVE_TOPOLOGY_TRIANGLELIST
case driver.TopologyTriangleStrip:
topology = d3d11.PRIMITIVE_TOPOLOGY_TRIANGLESTRIP
default:
panic("unsupported draw mode")
}
b.ctx.IASetPrimitiveTopology(topology)
}
func (b *Backend) BindImageTexture(unit int, tex driver.Texture) {
t := tex.(*Texture)
if t.uaView != nil {
b.ctx.CSSetUnorderedAccessViews(uint32(unit), t.uaView)
} else {
b.ctx.CSSetShaderResources(uint32(unit), t.resView)
}
}
func (b *Backend) DispatchCompute(x, y, z int) {
b.ctx.CSSetShader(b.program.shader)
b.ctx.Dispatch(uint32(x), uint32(y), uint32(z))
}
func (t *Texture) Upload(offset, size image.Point, pixels []byte, stride int) {
if stride == 0 {
stride = size.X * 4
}
dst := &d3d11.BOX{
Left: uint32(offset.X),
Top: uint32(offset.Y),
Right: uint32(offset.X + size.X),
Bottom: uint32(offset.Y + size.Y),
Front: 0,
Back: 1,
}
res := (*d3d11.Resource)(unsafe.Pointer(t.tex))
t.backend.ctx.UpdateSubresource(res, dst, uint32(stride), uint32(len(pixels)), pixels)
if t.mipmap {
t.backend.ctx.GenerateMips(t.resView)
}
}
func (t *Texture) Release() {
if t.foreign {
panic("texture not created by NewTexture")
}
if t.renderTarget != nil {
d3d11.IUnknownRelease(unsafe.Pointer(t.renderTarget), t.renderTarget.Vtbl.Release)
}
if t.sampler != nil {
d3d11.IUnknownRelease(unsafe.Pointer(t.sampler), t.sampler.Vtbl.Release)
}
if t.resView != nil {
d3d11.IUnknownRelease(unsafe.Pointer(t.resView), t.resView.Vtbl.Release)
}
if t.uaView != nil {
d3d11.IUnknownRelease(unsafe.Pointer(t.uaView), t.uaView.Vtbl.Release)
}
d3d11.IUnknownRelease(unsafe.Pointer(t.tex), t.tex.Vtbl.Release)
*t = Texture{}
}
func (b *Backend) PrepareTexture(tex driver.Texture) {}
func (b *Backend) BindTexture(unit int, tex driver.Texture) {
t := tex.(*Texture)
b.ctx.PSSetSamplers(uint32(unit), t.sampler)
b.ctx.PSSetShaderResources(uint32(unit), t.resView)
}
func (b *Backend) BindPipeline(pipe driver.Pipeline) {
b.pipeline = pipe.(*Pipeline)
}
func (b *Backend) BindProgram(prog driver.Program) {
b.program = prog.(*Program)
}
func (s *VertexShader) Release() {
d3d11.IUnknownRelease(unsafe.Pointer(s.shader), s.shader.Vtbl.Release)
*s = VertexShader{}
}
func (s *FragmentShader) Release() {
d3d11.IUnknownRelease(unsafe.Pointer(s.shader), s.shader.Vtbl.Release)
*s = FragmentShader{}
}
func (s *Program) Release() {
d3d11.IUnknownRelease(unsafe.Pointer(s.shader), s.shader.Vtbl.Release)
*s = Program{}
}
func (p *Pipeline) Release() {
d3d11.IUnknownRelease(unsafe.Pointer(p.vert), p.vert.Vtbl.Release)
d3d11.IUnknownRelease(unsafe.Pointer(p.frag), p.frag.Vtbl.Release)
d3d11.IUnknownRelease(unsafe.Pointer(p.blend), p.blend.Vtbl.Release)
if l := p.layout; l != nil {
d3d11.IUnknownRelease(unsafe.Pointer(l), l.Vtbl.Release)
}
*p = Pipeline{}
}
func (b *Backend) BindStorageBuffer(binding int, buffer driver.Buffer) {
buf := buffer.(*Buffer)
if buf.resView != nil {
b.ctx.CSSetShaderResources(uint32(binding), buf.resView)
} else {
b.ctx.CSSetUnorderedAccessViews(uint32(binding), buf.uaView)
}
}
func (b *Backend) BindUniforms(buffer driver.Buffer) {
buf := buffer.(*Buffer)
b.ctx.VSSetConstantBuffers(buf.buf)
b.ctx.PSSetConstantBuffers(buf.buf)
}
func (b *Backend) BindVertexBuffer(buf driver.Buffer, offset int) {
b.vert.buffer = buf.(*Buffer)
b.vert.offset = offset
}
func (b *Backend) BindIndexBuffer(buf driver.Buffer) {
b.ctx.IASetIndexBuffer(buf.(*Buffer).buf, d3d11.DXGI_FORMAT_R16_UINT, 0)
}
func (b *Buffer) Download(dst []byte) error {
res := (*d3d11.Resource)(unsafe.Pointer(b.buf))
resMap, err := b.backend.ctx.Map(res, 0, d3d11.MAP_READ, 0)
if err != nil {
return fmt.Errorf("d3d11: %v", err)
}
defer b.backend.ctx.Unmap(res, 0)
data := sliceOf(resMap.PData, len(dst))
copy(dst, data)
return nil
}
func (b *Buffer) Upload(data []byte) {
var dst *d3d11.BOX
if len(data) < b.size {
dst = &d3d11.BOX{
Left: 0,
Right: uint32(len(data)),
Top: 0,
Bottom: 1,
Front: 0,
Back: 1,
}
}
b.backend.ctx.UpdateSubresource((*d3d11.Resource)(unsafe.Pointer(b.buf)), dst, 0, 0, data)
}
func (b *Buffer) Release() {
if b.resView != nil {
d3d11.IUnknownRelease(unsafe.Pointer(b.resView), b.resView.Vtbl.Release)
}
if b.uaView != nil {
d3d11.IUnknownRelease(unsafe.Pointer(b.uaView), b.uaView.Vtbl.Release)
}
d3d11.IUnknownRelease(unsafe.Pointer(b.buf), b.buf.Vtbl.Release)
*b = Buffer{}
}
func (t *Texture) ReadPixels(src image.Rectangle, pixels []byte, stride int) error {
w, h := src.Dx(), src.Dy()
tex, err := t.backend.dev.CreateTexture2D(&d3d11.TEXTURE2D_DESC{
Width: uint32(w),
Height: uint32(h),
MipLevels: 1,
ArraySize: 1,
Format: t.format,
SampleDesc: d3d11.DXGI_SAMPLE_DESC{
Count: 1,
Quality: 0,
},
Usage: d3d11.USAGE_STAGING,
CPUAccessFlags: d3d11.CPU_ACCESS_READ,
})
if err != nil {
return fmt.Errorf("ReadPixels: %v", err)
}
defer d3d11.IUnknownRelease(unsafe.Pointer(tex), tex.Vtbl.Release)
res := (*d3d11.Resource)(unsafe.Pointer(tex))
t.backend.ctx.CopySubresourceRegion(
res,
0, // Destination subresource.
0, 0, 0, // Destination coordinates (x, y, z).
(*d3d11.Resource)(t.tex),
0, // Source subresource.
&d3d11.BOX{
Left: uint32(src.Min.X),
Top: uint32(src.Min.Y),
Right: uint32(src.Max.X),
Bottom: uint32(src.Max.Y),
Front: 0,
Back: 1,
},
)
resMap, err := t.backend.ctx.Map(res, 0, d3d11.MAP_READ, 0)
if err != nil {
return fmt.Errorf("ReadPixels: %v", err)
}
defer t.backend.ctx.Unmap(res, 0)
srcPitch := stride
dstPitch := int(resMap.RowPitch)
mapSize := dstPitch * h
data := sliceOf(resMap.PData, mapSize)
width := w * 4
for r := range h {
pixels := pixels[r*srcPitch:]
copy(pixels[:width], data[r*dstPitch:])
}
return nil
}
func (b *Backend) BeginCompute() {
}
func (b *Backend) EndCompute() {
}
func (b *Backend) BeginRenderPass(tex driver.Texture, d driver.LoadDesc) {
t := tex.(*Texture)
b.ctx.OMSetRenderTargets(t.renderTarget, nil)
if d.Action == driver.LoadActionClear {
c := d.ClearColor
b.clearColor = [4]float32{c.R, c.G, c.B, c.A}
b.ctx.ClearRenderTargetView(t.renderTarget, &b.clearColor)
}
}
func (b *Backend) EndRenderPass() {
}
func (f *Texture) ImplementsRenderTarget() {}
func convBufferBinding(typ driver.BufferBinding) uint32 {
var bindings uint32
if typ&driver.BufferBindingVertices != 0 {
bindings |= d3d11.BIND_VERTEX_BUFFER
}
if typ&driver.BufferBindingIndices != 0 {
bindings |= d3d11.BIND_INDEX_BUFFER
}
if typ&driver.BufferBindingUniforms != 0 {
bindings |= d3d11.BIND_CONSTANT_BUFFER
}
if typ&driver.BufferBindingTexture != 0 {
bindings |= d3d11.BIND_SHADER_RESOURCE
}
if typ&driver.BufferBindingFramebuffer != 0 {
bindings |= d3d11.BIND_RENDER_TARGET
}
if typ&driver.BufferBindingShaderStorageWrite != 0 {
bindings |= d3d11.BIND_UNORDERED_ACCESS
} else if typ&driver.BufferBindingShaderStorageRead != 0 {
bindings |= d3d11.BIND_SHADER_RESOURCE
}
return bindings
}
func toBlendFactor(f driver.BlendFactor) (uint32, uint32) {
switch f {
case driver.BlendFactorOne:
return d3d11.BLEND_ONE, d3d11.BLEND_ONE
case driver.BlendFactorOneMinusSrcAlpha:
return d3d11.BLEND_INV_SRC_ALPHA, d3d11.BLEND_INV_SRC_ALPHA
case driver.BlendFactorZero:
return d3d11.BLEND_ZERO, d3d11.BLEND_ZERO
case driver.BlendFactorDstColor:
return d3d11.BLEND_DEST_COLOR, d3d11.BLEND_DEST_ALPHA
default:
panic("unsupported blend source factor")
}
}
// sliceOf returns a slice from a (native) pointer.
func sliceOf(ptr uintptr, cap int) []byte {
return unsafe.Slice((*byte)(unsafe.Pointer(ptr)), cap)
}
+129
View File
@@ -0,0 +1,129 @@
// SPDX-License-Identifier: Unlicense OR MIT
package driver
import (
"fmt"
"unsafe"
"gioui.org/internal/gl"
)
// See gpu/api.go for documentation for the API types.
type API interface {
implementsAPI()
}
type RenderTarget interface {
ImplementsRenderTarget()
}
type OpenGLRenderTarget gl.Framebuffer
type Direct3D11RenderTarget struct {
// RenderTarget is a *ID3D11RenderTargetView.
RenderTarget unsafe.Pointer
}
type MetalRenderTarget struct {
// Texture is a MTLTexture.
Texture uintptr
}
type VulkanRenderTarget struct {
// WaitSem is a VkSemaphore that must signaled before accessing Framebuffer.
WaitSem uint64
// SignalSem is a VkSemaphore that signal access to Framebuffer is complete.
SignalSem uint64
// Fence is a VkFence that is set when all commands to Framebuffer has completed.
Fence uint64
// Image is the VkImage to render into.
Image uint64
// Framebuffer is a VkFramebuffer for Image.
Framebuffer uint64
}
type OpenGL struct {
// ES forces the use of ANGLE OpenGL ES libraries on macOS. It is
// ignored on all other platforms.
ES bool
// Context contains the WebGL context for WebAssembly platforms. It is
// empty for all other platforms; an OpenGL context is assumed current when
// calling NewDevice.
Context gl.Context
// Shared instructs users of the context to restore the GL state after
// use.
Shared bool
}
type Direct3D11 struct {
// Device contains a *ID3D11Device.
Device unsafe.Pointer
}
type Metal struct {
// Device is an MTLDevice.
Device uintptr
// Queue is a MTLCommandQueue.
Queue uintptr
// PixelFormat is the MTLPixelFormat of the default framebuffer.
PixelFormat int
}
type Vulkan struct {
// PhysDevice is a VkPhysicalDevice.
PhysDevice unsafe.Pointer
// Device is a VkDevice.
Device unsafe.Pointer
// QueueFamily is the queue familily index of the queue.
QueueFamily int
// QueueIndex is the logical queue index of the queue.
QueueIndex int
// Format is a VkFormat that matches render targets.
Format int
}
// API specific device constructors.
var (
NewOpenGLDevice func(api OpenGL) (Device, error)
NewDirect3D11Device func(api Direct3D11) (Device, error)
NewMetalDevice func(api Metal) (Device, error)
NewVulkanDevice func(api Vulkan) (Device, error)
)
// NewDevice creates a new Device given the api.
//
// Note that the device does not assume ownership of the resources contained in
// api; the caller must ensure the resources are valid until the device is
// released.
func NewDevice(api API) (Device, error) {
switch api := api.(type) {
case OpenGL:
if NewOpenGLDevice != nil {
return NewOpenGLDevice(api)
}
case Direct3D11:
if NewDirect3D11Device != nil {
return NewDirect3D11Device(api)
}
case Metal:
if NewMetalDevice != nil {
return NewMetalDevice(api)
}
case Vulkan:
if NewVulkanDevice != nil {
return NewVulkanDevice(api)
}
}
return nil, fmt.Errorf("driver: no driver available for the API %T", api)
}
func (OpenGL) implementsAPI() {}
func (Direct3D11) implementsAPI() {}
func (Metal) implementsAPI() {}
func (Vulkan) implementsAPI() {}
func (OpenGLRenderTarget) ImplementsRenderTarget() {}
func (Direct3D11RenderTarget) ImplementsRenderTarget() {}
func (MetalRenderTarget) ImplementsRenderTarget() {}
func (VulkanRenderTarget) ImplementsRenderTarget() {}
+240
View File
@@ -0,0 +1,240 @@
// SPDX-License-Identifier: Unlicense OR MIT
package driver
import (
"errors"
"image"
"time"
"gioui.org/internal/f32color"
"gioui.org/shader"
)
// Device represents the abstraction of underlying GPU
// APIs such as OpenGL, Direct3D useful for rendering Gio
// operations.
type Device interface {
BeginFrame(target RenderTarget, clear bool, viewport image.Point) Texture
EndFrame()
Caps() Caps
NewTimer() Timer
// IsContinuousTime reports whether all timer measurements
// are valid at the point of call.
IsTimeContinuous() bool
NewTexture(format TextureFormat, width, height int, minFilter, magFilter TextureFilter, bindings BufferBinding) (Texture, error)
NewImmutableBuffer(typ BufferBinding, data []byte) (Buffer, error)
NewBuffer(typ BufferBinding, size int) (Buffer, error)
NewComputeProgram(shader shader.Sources) (Program, error)
NewVertexShader(src shader.Sources) (VertexShader, error)
NewFragmentShader(src shader.Sources) (FragmentShader, error)
NewPipeline(desc PipelineDesc) (Pipeline, error)
Viewport(x, y, width, height int)
DrawArrays(off, count int)
DrawElements(off, count int)
BeginRenderPass(t Texture, desc LoadDesc)
EndRenderPass()
PrepareTexture(t Texture)
BindProgram(p Program)
BindPipeline(p Pipeline)
BindTexture(unit int, t Texture)
BindVertexBuffer(b Buffer, offset int)
BindIndexBuffer(b Buffer)
BindImageTexture(unit int, texture Texture)
BindUniforms(buf Buffer)
BindStorageBuffer(binding int, buf Buffer)
BeginCompute()
EndCompute()
CopyTexture(dst Texture, dstOrigin image.Point, src Texture, srcRect image.Rectangle)
DispatchCompute(x, y, z int)
Release()
}
var ErrDeviceLost = errors.New("GPU device lost")
type LoadDesc struct {
Action LoadAction
ClearColor f32color.RGBA
}
type Pipeline interface {
Release()
}
type PipelineDesc struct {
VertexShader VertexShader
FragmentShader FragmentShader
VertexLayout VertexLayout
BlendDesc BlendDesc
PixelFormat TextureFormat
Topology Topology
}
type VertexLayout struct {
Inputs []InputDesc
Stride int
}
// InputDesc describes a vertex attribute as laid out in a Buffer.
type InputDesc struct {
Type shader.DataType
Size int
Offset int
}
type BlendDesc struct {
Enable bool
SrcFactor, DstFactor BlendFactor
}
type BlendFactor uint8
type Topology uint8
type (
TextureFilter uint8
TextureFormat uint8
)
type BufferBinding uint8
type LoadAction uint8
type Features uint
type Caps struct {
// BottomLeftOrigin is true if the driver has the origin in the lower left
// corner. The OpenGL driver returns true.
BottomLeftOrigin bool
Features Features
MaxTextureSize int
}
type VertexShader interface {
Release()
}
type FragmentShader interface {
Release()
}
type Program interface {
Release()
}
type Buffer interface {
Release()
Upload(data []byte)
Download(data []byte) error
}
type Timer interface {
Begin()
End()
Duration() (time.Duration, bool)
Release()
}
type Texture interface {
RenderTarget
Upload(offset, size image.Point, pixels []byte, stride int)
ReadPixels(src image.Rectangle, pixels []byte, stride int) error
Release()
}
const (
BufferBindingIndices BufferBinding = 1 << iota
BufferBindingVertices
BufferBindingUniforms
BufferBindingTexture
BufferBindingFramebuffer
BufferBindingShaderStorageRead
BufferBindingShaderStorageWrite
)
const (
TextureFormatSRGBA TextureFormat = iota
TextureFormatFloat
TextureFormatRGBA8
// TextureFormatOutput denotes the format used by the output framebuffer.
TextureFormatOutput
)
const (
FilterNearest TextureFilter = iota
FilterLinear
FilterLinearMipmapLinear
)
const (
FeatureTimers Features = 1 << iota
FeatureFloatRenderTargets
FeatureCompute
FeatureSRGB
)
const (
TopologyTriangleStrip Topology = iota
TopologyTriangles
)
const (
BlendFactorOne BlendFactor = iota
BlendFactorOneMinusSrcAlpha
BlendFactorZero
BlendFactorDstColor
)
const (
LoadActionKeep LoadAction = iota
LoadActionClear
LoadActionInvalidate
)
var ErrContentLost = errors.New("buffer content lost")
func (f Features) Has(feats Features) bool {
return f&feats == feats
}
func DownloadImage(d Device, t Texture, img *image.RGBA) error {
r := img.Bounds()
if err := t.ReadPixels(r, img.Pix, img.Stride); err != nil {
return err
}
if d.Caps().BottomLeftOrigin {
// OpenGL origin is in the lower-left corner. Flip the image to
// match.
flipImageY(r.Dx()*4, r.Dy(), img.Pix)
}
return nil
}
func flipImageY(stride, height int, pixels []byte) {
// Flip image in y-direction. OpenGL's origin is in the lower
// left corner.
row := make([]uint8, stride)
for y := range height / 2 {
y1 := height - y - 1
dest := y1 * stride
src := y * stride
copy(row, pixels[dest:])
copy(pixels[dest:], pixels[src:src+len(row)])
copy(pixels[src:], row)
}
}
func UploadImage(t Texture, offset image.Point, img *image.RGBA) {
var pixels []byte
size := img.Bounds().Size()
min := img.Rect.Min
start := img.PixOffset(min.X, min.Y)
end := img.PixOffset(min.X+size.X, min.Y+size.Y-1)
pixels = img.Pix[start:end]
t.Upload(offset, size, pixels, img.Stride)
}
+5
View File
@@ -0,0 +1,5 @@
// SPDX-License-Identifier: Unlicense OR MIT
// This file exists so this package builds on non-Darwin platforms.
package metal
File diff suppressed because it is too large Load Diff
+1374
View File
File diff suppressed because it is too large Load Diff
+176
View File
@@ -0,0 +1,176 @@
// SPDX-License-Identifier: Unlicense OR MIT
package opengl
import (
"errors"
"fmt"
"image"
"runtime"
"strings"
"gioui.org/internal/byteslice"
"gioui.org/internal/gl"
)
// SRGBFBO implements an intermediate sRGB FBO
// for gamma-correct rendering on platforms without
// sRGB enabled native framebuffers.
type SRGBFBO struct {
c *gl.Functions
state *glState
viewport image.Point
fbo gl.Framebuffer
tex gl.Texture
blitted bool
quad gl.Buffer
prog gl.Program
format textureTriple
}
func NewSRGBFBO(f *gl.Functions, state *glState) (*SRGBFBO, error) {
glVer := f.GetString(gl.VERSION)
ver, _, err := gl.ParseGLVersion(glVer)
if err != nil {
return nil, err
}
exts := strings.Split(f.GetString(gl.EXTENSIONS), " ")
srgbTriple, err := srgbaTripleFor(ver, exts)
if err != nil {
// Fall back to the linear RGB colorspace, at the cost of color precision loss.
srgbTriple = textureTriple{gl.RGBA, gl.Enum(gl.RGBA), gl.Enum(gl.UNSIGNED_BYTE)}
}
s := &SRGBFBO{
c: f,
state: state,
format: srgbTriple,
fbo: f.CreateFramebuffer(),
tex: f.CreateTexture(),
}
state.bindTexture(f, 0, s.tex)
f.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)
f.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE)
f.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST)
f.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST)
return s, nil
}
func (s *SRGBFBO) Blit() {
if !s.blitted {
prog, err := gl.CreateProgram(s.c, blitVSrc, blitFSrc, []string{"pos", "uv"})
if err != nil {
panic(err)
}
s.prog = prog
s.state.useProgram(s.c, prog)
s.c.Uniform1i(s.c.GetUniformLocation(prog, "tex"), 0)
s.quad = s.c.CreateBuffer()
s.state.bindBuffer(s.c, gl.ARRAY_BUFFER, s.quad)
coords := byteslice.Slice([]float32{
-1, +1, 0, 1,
+1, +1, 1, 1,
-1, -1, 0, 0,
+1, -1, 1, 0,
})
s.c.BufferData(gl.ARRAY_BUFFER, len(coords), gl.STATIC_DRAW, coords)
s.blitted = true
}
s.state.useProgram(s.c, s.prog)
s.state.bindTexture(s.c, 0, s.tex)
s.state.vertexAttribPointer(s.c, s.quad, 0 /* pos */, 2, gl.FLOAT, false, 4*4, 0)
s.state.vertexAttribPointer(s.c, s.quad, 1 /* uv */, 2, gl.FLOAT, false, 4*4, 4*2)
s.state.setVertexAttribArray(s.c, 0, true)
s.state.setVertexAttribArray(s.c, 1, true)
s.c.DrawArrays(gl.TRIANGLE_STRIP, 0, 4)
s.state.bindFramebuffer(s.c, gl.FRAMEBUFFER, s.fbo)
s.c.InvalidateFramebuffer(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0)
}
func (s *SRGBFBO) Framebuffer() gl.Framebuffer {
return s.fbo
}
func (s *SRGBFBO) Refresh(viewport image.Point) error {
if viewport.X == 0 || viewport.Y == 0 {
return errors.New("srgb: zero-sized framebuffer")
}
if s.viewport == viewport {
return nil
}
s.viewport = viewport
s.state.bindTexture(s.c, 0, s.tex)
s.c.TexImage2D(gl.TEXTURE_2D, 0, s.format.internalFormat, viewport.X, viewport.Y, s.format.format, s.format.typ)
s.state.bindFramebuffer(s.c, gl.FRAMEBUFFER, s.fbo)
s.c.FramebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, s.tex, 0)
if st := s.c.CheckFramebufferStatus(gl.FRAMEBUFFER); st != gl.FRAMEBUFFER_COMPLETE {
return fmt.Errorf("sRGB framebuffer incomplete (%dx%d), status: %#x error: %x", viewport.X, viewport.Y, st, s.c.GetError())
}
if runtime.GOOS == "js" {
// With macOS Safari, rendering to and then reading from a SRGB8_ALPHA8
// texture result in twice gamma corrected colors. Using a plain RGBA
// texture seems to work.
s.state.setClearColor(s.c, .5, .5, .5, 1.0)
s.c.Clear(gl.COLOR_BUFFER_BIT)
var pixel [4]byte
s.c.ReadPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, pixel[:])
if pixel[0] == 128 { // Correct sRGB color value is ~188
s.c.TexImage2D(gl.TEXTURE_2D, 0, gl.RGBA, viewport.X, viewport.Y, gl.RGBA, gl.UNSIGNED_BYTE)
if st := s.c.CheckFramebufferStatus(gl.FRAMEBUFFER); st != gl.FRAMEBUFFER_COMPLETE {
return fmt.Errorf("fallback RGBA framebuffer incomplete (%dx%d), status: %#x error: %x", viewport.X, viewport.Y, st, s.c.GetError())
}
}
}
return nil
}
func (s *SRGBFBO) Release() {
s.state.deleteFramebuffer(s.c, s.fbo)
s.state.deleteTexture(s.c, s.tex)
if s.blitted {
s.state.deleteBuffer(s.c, s.quad)
s.state.deleteProgram(s.c, s.prog)
}
s.c = nil
}
const (
blitVSrc = `
#version 100
precision highp float;
attribute vec2 pos;
attribute vec2 uv;
varying vec2 vUV;
void main() {
gl_Position = vec4(pos, 0, 1);
vUV = uv;
}
`
blitFSrc = `
#version 100
precision mediump float;
uniform sampler2D tex;
varying vec2 vUV;
vec3 gamma(vec3 rgb) {
vec3 exp = vec3(1.055)*pow(rgb, vec3(0.41666)) - vec3(0.055);
vec3 lin = rgb * vec3(12.92);
bvec3 cut = lessThan(rgb, vec3(0.0031308));
return vec3(cut.r ? lin.r : exp.r, cut.g ? lin.g : exp.g, cut.b ? lin.b : exp.b);
}
void main() {
vec4 col = texture2D(tex, vUV);
vec3 rgb = col.rgb;
rgb = gamma(rgb);
gl_FragColor = vec4(rgb, col.a);
}
`
)
+1163
View File
File diff suppressed because it is too large Load Diff
+5
View File
@@ -0,0 +1,5 @@
// SPDX-License-Identifier: Unlicense OR MIT
package vulkan
// Empty file to avoid the build error for platforms without Vulkan support.
+109
View File
@@ -0,0 +1,109 @@
// SPDX-License-Identifier: Unlicense OR MIT
package gpu
import (
"image"
)
// packer packs a set of many smaller rectangles into
// much fewer larger atlases.
type packer struct {
maxDims image.Point
spaces []image.Rectangle
sizes []image.Point
pos image.Point
}
type placement struct {
Idx int
Pos image.Point
}
// add adds the given rectangle to the atlases and
// return the allocated position.
func (p *packer) add(s image.Point) (placement, bool) {
if place, ok := p.tryAdd(s); ok {
return place, true
}
p.newPage()
return p.tryAdd(s)
}
func (p *packer) clear() {
p.sizes = p.sizes[:0]
p.spaces = p.spaces[:0]
}
func (p *packer) newPage() {
p.pos = image.Point{}
p.sizes = append(p.sizes, image.Point{})
p.spaces = p.spaces[:0]
p.spaces = append(p.spaces, image.Rectangle{
Max: image.Point{X: 1e6, Y: 1e6},
})
}
func (p *packer) tryAdd(s image.Point) (placement, bool) {
if len(p.spaces) == 0 || len(p.sizes) == 0 {
return placement{}, false
}
var (
bestIdx *image.Rectangle
bestSize = p.maxDims
lastSize = p.sizes[len(p.sizes)-1]
)
// Go backwards to prioritize smaller spaces.
for i := range p.spaces {
space := &p.spaces[i]
rightSpace := space.Dx() - s.X
bottomSpace := space.Dy() - s.Y
if rightSpace < 0 || bottomSpace < 0 {
continue
}
size := lastSize
if x := space.Min.X + s.X; x > size.X {
if x > p.maxDims.X {
continue
}
size.X = x
}
if y := space.Min.Y + s.Y; y > size.Y {
if y > p.maxDims.Y {
continue
}
size.Y = y
}
if size.X*size.Y < bestSize.X*bestSize.Y {
bestIdx = space
bestSize = size
}
}
if bestIdx == nil {
return placement{}, false
}
// Remove space.
bestSpace := *bestIdx
*bestIdx = p.spaces[len(p.spaces)-1]
p.spaces = p.spaces[:len(p.spaces)-1]
// Put s in the top left corner and add the (at most)
// two smaller spaces.
pos := bestSpace.Min
if rem := bestSpace.Dy() - s.Y; rem > 0 {
p.spaces = append(p.spaces, image.Rectangle{
Min: image.Point{X: pos.X, Y: pos.Y + s.Y},
Max: image.Point{X: bestSpace.Max.X, Y: bestSpace.Max.Y},
})
}
if rem := bestSpace.Dx() - s.X; rem > 0 {
p.spaces = append(p.spaces, image.Rectangle{
Min: image.Point{X: pos.X + s.X, Y: pos.Y},
Max: image.Point{X: bestSpace.Max.X, Y: pos.Y + s.Y},
})
}
idx := len(p.sizes) - 1
p.sizes[idx] = bestSize
return placement{Idx: idx, Pos: pos}, true
}
+424
View File
@@ -0,0 +1,424 @@
// SPDX-License-Identifier: Unlicense OR MIT
package gpu
// GPU accelerated path drawing using the algorithms from
// Pathfinder (https://github.com/servo/pathfinder).
import (
"encoding/binary"
"image"
"math"
"unsafe"
"gioui.org/gpu/internal/driver"
"gioui.org/internal/byteslice"
"gioui.org/internal/f32"
"gioui.org/internal/f32color"
"gioui.org/shader"
"gioui.org/shader/gio"
)
type pather struct {
ctx driver.Device
viewport image.Point
stenciler *stenciler
coverer *coverer
}
type coverer struct {
ctx driver.Device
pipelines [2][3]*pipeline
texUniforms *coverTexUniforms
colUniforms *coverColUniforms
linearGradientUniforms *coverLinearGradientUniforms
}
type coverTexUniforms struct {
coverUniforms
_ [12]byte // Padding to multiple of 16.
}
type coverColUniforms struct {
coverUniforms
_ [128 - unsafe.Sizeof(coverUniforms{}) - unsafe.Sizeof(colorUniforms{})]byte // Padding to 128 bytes.
colorUniforms
}
type coverLinearGradientUniforms struct {
coverUniforms
_ [128 - unsafe.Sizeof(coverUniforms{}) - unsafe.Sizeof(gradientUniforms{})]byte // Padding to 128.
gradientUniforms
}
type coverUniforms struct {
transform [4]float32
uvCoverTransform [4]float32
uvTransformR1 [4]float32
uvTransformR2 [4]float32
fbo float32
}
type stenciler struct {
ctx driver.Device
pipeline struct {
pipeline *pipeline
uniforms *stencilUniforms
}
ipipeline struct {
pipeline *pipeline
uniforms *intersectUniforms
}
fbos fboSet
intersections fboSet
indexBuf driver.Buffer
}
type stencilUniforms struct {
transform [4]float32
pathOffset [2]float32
_ [8]byte // Padding to multiple of 16.
}
type intersectUniforms struct {
vert struct {
uvTransform [4]float32
subUVTransform [4]float32
}
}
type fboSet struct {
fbos []FBO
}
type FBO struct {
size image.Point
tex driver.Texture
}
type pathData struct {
ncurves int
data driver.Buffer
}
// vertex data suitable for passing to vertex programs.
type vertex struct {
// Corner encodes the corner: +0.5 for south, +.25 for east.
Corner float32
MaxY float32
FromX, FromY float32
CtrlX, CtrlY float32
ToX, ToY float32
}
// encode needs to stay in-sync with the code in clip.go encodeQuadTo.
func (v vertex) encode(d []byte, maxy uint32) {
d = d[0:32]
bo := binary.LittleEndian
bo.PutUint32(d[0:4], math.Float32bits(v.Corner))
bo.PutUint32(d[4:8], maxy)
bo.PutUint32(d[8:12], math.Float32bits(v.FromX))
bo.PutUint32(d[12:16], math.Float32bits(v.FromY))
bo.PutUint32(d[16:20], math.Float32bits(v.CtrlX))
bo.PutUint32(d[20:24], math.Float32bits(v.CtrlY))
bo.PutUint32(d[24:28], math.Float32bits(v.ToX))
bo.PutUint32(d[28:32], math.Float32bits(v.ToY))
}
const (
// Number of path quads per draw batch.
pathBatchSize = 10000
// Size of a vertex as sent to gpu
vertStride = 8 * 4
)
func newPather(ctx driver.Device) *pather {
return &pather{
ctx: ctx,
stenciler: newStenciler(ctx),
coverer: newCoverer(ctx),
}
}
func newCoverer(ctx driver.Device) *coverer {
c := &coverer{
ctx: ctx,
}
c.colUniforms = new(coverColUniforms)
c.texUniforms = new(coverTexUniforms)
c.linearGradientUniforms = new(coverLinearGradientUniforms)
pipelines, err := createColorPrograms(ctx, gio.Shader_cover_vert, gio.Shader_cover_frag,
[3]any{c.colUniforms, c.linearGradientUniforms, c.texUniforms},
)
if err != nil {
panic(err)
}
c.pipelines = pipelines
return c
}
func newStenciler(ctx driver.Device) *stenciler {
// Allocate a suitably large index buffer for drawing paths.
indices := make([]uint16, pathBatchSize*6)
for i := range pathBatchSize {
i := uint16(i)
indices[i*6+0] = i*4 + 0
indices[i*6+1] = i*4 + 1
indices[i*6+2] = i*4 + 2
indices[i*6+3] = i*4 + 2
indices[i*6+4] = i*4 + 1
indices[i*6+5] = i*4 + 3
}
indexBuf, err := ctx.NewImmutableBuffer(driver.BufferBindingIndices, byteslice.Slice(indices))
if err != nil {
panic(err)
}
progLayout := driver.VertexLayout{
Inputs: []driver.InputDesc{
{Type: shader.DataTypeFloat, Size: 1, Offset: int(unsafe.Offsetof((*(*vertex)(nil)).Corner))},
{Type: shader.DataTypeFloat, Size: 1, Offset: int(unsafe.Offsetof((*(*vertex)(nil)).MaxY))},
{Type: shader.DataTypeFloat, Size: 2, Offset: int(unsafe.Offsetof((*(*vertex)(nil)).FromX))},
{Type: shader.DataTypeFloat, Size: 2, Offset: int(unsafe.Offsetof((*(*vertex)(nil)).CtrlX))},
{Type: shader.DataTypeFloat, Size: 2, Offset: int(unsafe.Offsetof((*(*vertex)(nil)).ToX))},
},
Stride: vertStride,
}
iprogLayout := driver.VertexLayout{
Inputs: []driver.InputDesc{
{Type: shader.DataTypeFloat, Size: 2, Offset: 0},
{Type: shader.DataTypeFloat, Size: 2, Offset: 4 * 2},
},
Stride: 4 * 4,
}
st := &stenciler{
ctx: ctx,
indexBuf: indexBuf,
}
vsh, fsh, err := newShaders(ctx, gio.Shader_stencil_vert, gio.Shader_stencil_frag)
if err != nil {
panic(err)
}
defer vsh.Release()
defer fsh.Release()
st.pipeline.uniforms = new(stencilUniforms)
vertUniforms := newUniformBuffer(ctx, st.pipeline.uniforms)
pipe, err := st.ctx.NewPipeline(driver.PipelineDesc{
VertexShader: vsh,
FragmentShader: fsh,
VertexLayout: progLayout,
BlendDesc: driver.BlendDesc{
Enable: true,
SrcFactor: driver.BlendFactorOne,
DstFactor: driver.BlendFactorOne,
},
PixelFormat: driver.TextureFormatFloat,
Topology: driver.TopologyTriangles,
})
st.pipeline.pipeline = &pipeline{pipe, vertUniforms}
if err != nil {
panic(err)
}
vsh, fsh, err = newShaders(ctx, gio.Shader_intersect_vert, gio.Shader_intersect_frag)
if err != nil {
panic(err)
}
defer vsh.Release()
defer fsh.Release()
st.ipipeline.uniforms = new(intersectUniforms)
vertUniforms = newUniformBuffer(ctx, &st.ipipeline.uniforms.vert)
ipipe, err := st.ctx.NewPipeline(driver.PipelineDesc{
VertexShader: vsh,
FragmentShader: fsh,
VertexLayout: iprogLayout,
BlendDesc: driver.BlendDesc{
Enable: true,
SrcFactor: driver.BlendFactorDstColor,
DstFactor: driver.BlendFactorZero,
},
PixelFormat: driver.TextureFormatFloat,
Topology: driver.TopologyTriangleStrip,
})
st.ipipeline.pipeline = &pipeline{ipipe, vertUniforms}
if err != nil {
panic(err)
}
return st
}
func (s *fboSet) resize(ctx driver.Device, format driver.TextureFormat, sizes []image.Point) {
// Add fbos.
for i := len(s.fbos); i < len(sizes); i++ {
s.fbos = append(s.fbos, FBO{})
}
// Resize fbos.
for i, sz := range sizes {
f := &s.fbos[i]
// Resizing or recreating FBOs can introduce rendering stalls.
// Avoid if the space waste is not too high.
resize := sz.X > f.size.X || sz.Y > f.size.Y
waste := float32(sz.X*sz.Y) / float32(f.size.X*f.size.Y)
resize = resize || waste > 1.2
if resize {
if f.tex != nil {
f.tex.Release()
}
// Add 5% extra space in each dimension to minimize resizing.
sz = sz.Mul(105).Div(100)
max := ctx.Caps().MaxTextureSize
if sz.Y > max {
sz.Y = max
}
if sz.X > max {
sz.X = max
}
tex, err := ctx.NewTexture(format, sz.X, sz.Y, driver.FilterNearest, driver.FilterNearest,
driver.BufferBindingTexture|driver.BufferBindingFramebuffer)
if err != nil {
panic(err)
}
f.size = sz
f.tex = tex
}
}
// Delete extra fbos.
s.delete(ctx, len(sizes))
}
func (s *fboSet) delete(ctx driver.Device, idx int) {
for i := idx; i < len(s.fbos); i++ {
f := s.fbos[i]
f.tex.Release()
}
s.fbos = s.fbos[:idx]
}
func (s *stenciler) release() {
s.fbos.delete(s.ctx, 0)
s.intersections.delete(s.ctx, 0)
s.pipeline.pipeline.Release()
s.ipipeline.pipeline.Release()
s.indexBuf.Release()
}
func (p *pather) release() {
p.stenciler.release()
p.coverer.release()
}
func (c *coverer) release() {
for _, p := range c.pipelines {
for _, p := range p {
p.Release()
}
}
}
func buildPath(ctx driver.Device, p []byte) pathData {
buf, err := ctx.NewImmutableBuffer(driver.BufferBindingVertices, p)
if err != nil {
panic(err)
}
return pathData{
ncurves: len(p) / vertStride,
data: buf,
}
}
func (p pathData) release() {
p.data.Release()
}
func (p *pather) begin(sizes []image.Point) {
p.stenciler.begin(sizes)
}
func (p *pather) stencilPath(bounds image.Rectangle, offset f32.Point, uv image.Point, data pathData) {
p.stenciler.stencilPath(bounds, offset, uv, data)
}
func (s *stenciler) beginIntersect(sizes []image.Point) {
// 8 bit coverage is enough, but OpenGL ES only supports single channel
// floating point formats. Replace with GL_RGB+GL_UNSIGNED_BYTE if
// no floating point support is available.
s.intersections.resize(s.ctx, driver.TextureFormatFloat, sizes)
}
func (s *stenciler) cover(idx int) FBO {
return s.fbos.fbos[idx]
}
func (s *stenciler) begin(sizes []image.Point) {
s.fbos.resize(s.ctx, driver.TextureFormatFloat, sizes)
}
func (s *stenciler) stencilPath(bounds image.Rectangle, offset f32.Point, uv image.Point, data pathData) {
s.ctx.Viewport(uv.X, uv.Y, bounds.Dx(), bounds.Dy())
// Transform UI coordinates to OpenGL coordinates.
texSize := f32.Point{X: float32(bounds.Dx()), Y: float32(bounds.Dy())}
scale := f32.Point{X: 2 / texSize.X, Y: 2 / texSize.Y}
orig := f32.Point{X: -1 - float32(bounds.Min.X)*2/texSize.X, Y: -1 - float32(bounds.Min.Y)*2/texSize.Y}
s.pipeline.uniforms.transform = [4]float32{scale.X, scale.Y, orig.X, orig.Y}
s.pipeline.uniforms.pathOffset = [2]float32{offset.X, offset.Y}
s.pipeline.pipeline.UploadUniforms(s.ctx)
// Draw in batches that fit in uint16 indices.
start := 0
nquads := data.ncurves / 4
for start < nquads {
batch := nquads - start
if max := pathBatchSize; batch > max {
batch = max
}
off := vertStride * start * 4
s.ctx.BindVertexBuffer(data.data, off)
s.ctx.DrawElements(0, batch*6)
start += batch
}
}
func (p *pather) cover(mat materialType, isFBO bool, col f32color.RGBA, col1, col2 f32color.RGBA, scale, off f32.Point, uvTrans f32.Affine2D, coverScale, coverOff f32.Point) {
p.coverer.cover(mat, isFBO, col, col1, col2, scale, off, uvTrans, coverScale, coverOff)
}
func (c *coverer) cover(mat materialType, isFBO bool, col f32color.RGBA, col1, col2 f32color.RGBA, scale, off f32.Point, uvTrans f32.Affine2D, coverScale, coverOff f32.Point) {
var uniforms *coverUniforms
switch mat {
case materialColor:
c.colUniforms.color = col
uniforms = &c.colUniforms.coverUniforms
case materialLinearGradient:
c.linearGradientUniforms.color1 = col1
c.linearGradientUniforms.color2 = col2
t1, t2, t3, t4, t5, t6 := uvTrans.Elems()
c.linearGradientUniforms.uvTransformR1 = [4]float32{t1, t2, t3, 0}
c.linearGradientUniforms.uvTransformR2 = [4]float32{t4, t5, t6, 0}
uniforms = &c.linearGradientUniforms.coverUniforms
case materialTexture:
t1, t2, t3, t4, t5, t6 := uvTrans.Elems()
c.texUniforms.uvTransformR1 = [4]float32{t1, t2, t3, 0}
c.texUniforms.uvTransformR2 = [4]float32{t4, t5, t6, 0}
uniforms = &c.texUniforms.coverUniforms
}
uniforms.fbo = 0
if isFBO {
uniforms.fbo = 1
}
uniforms.transform = [4]float32{scale.X, scale.Y, off.X, off.Y}
uniforms.uvCoverTransform = [4]float32{coverScale.X, coverScale.Y, coverOff.X, coverOff.Y}
fboIdx := 0
if isFBO {
fboIdx = 1
}
c.pipelines[fboIdx][mat].UploadUniforms(c.ctx)
c.ctx.DrawArrays(0, 4)
}
func init() {
// Check that struct vertex has the expected size and
// that it contains no padding.
if unsafe.Sizeof(*(*vertex)(nil)) != vertStride {
panic("unexpected struct size")
}
}
+94
View File
@@ -0,0 +1,94 @@
// SPDX-License-Identifier: Unlicense OR MIT
package gpu
import (
"time"
"gioui.org/gpu/internal/driver"
)
type timers struct {
backend driver.Device
timers []*timer
}
type timer struct {
Elapsed time.Duration
backend driver.Device
timer driver.Timer
state timerState
}
type timerState uint8
const (
timerIdle timerState = iota
timerRunning
timerWaiting
)
func newTimers(b driver.Device) *timers {
return &timers{
backend: b,
}
}
func (t *timers) newTimer() *timer {
if t == nil {
return nil
}
tt := &timer{
backend: t.backend,
timer: t.backend.NewTimer(),
}
t.timers = append(t.timers, tt)
return tt
}
func (t *timer) begin() {
if t == nil || t.state != timerIdle {
return
}
t.timer.Begin()
t.state = timerRunning
}
func (t *timer) end() {
if t == nil || t.state != timerRunning {
return
}
t.timer.End()
t.state = timerWaiting
}
func (t *timers) ready() bool {
if t == nil {
return false
}
for _, tt := range t.timers {
switch tt.state {
case timerIdle:
continue
case timerRunning:
return false
}
d, ok := tt.timer.Duration()
if !ok {
return false
}
tt.state = timerIdle
tt.Elapsed = d
}
return t.backend.IsTimeContinuous()
}
func (t *timers) Release() {
if t == nil {
return
}
for _, tt := range t.timers {
tt.timer.Release()
}
t.timers = nil
}