mirror of
https://git.sr.ht/~eliasnaur/gio
synced 2026-07-02 07:57:29 +00:00
gpu,app,internal/glimpl: update GL backend for the compute renderer
Modern graphics APIs have immutable objects, with mutable data. For example, a texture's dimensions are immutable, while the texture contents is not. Change the GPU API abstraction to match. Clearing a Texture is convenient to do with a plain []byte. Generalize Texture.Upload to take a plain byte slice and introduce a helper function for uploading *image.RGBA data. Add TextureFormatRGBA8 a format for the linear RGB colorspace. Add OpenGL ES 3.1 functions for compute programs. Signed-off-by: Elias Naur <mail@eliasnaur.com>
This commit is contained in:
+48
-10
@@ -23,6 +23,7 @@ type Device interface {
|
||||
NewFramebuffer(tex Texture, depthBits int) (Framebuffer, error)
|
||||
NewImmutableBuffer(typ BufferBinding, data []byte) (Buffer, error)
|
||||
NewBuffer(typ BufferBinding, size int) (Buffer, error)
|
||||
NewComputeProgram(shader ShaderSources) (Program, error)
|
||||
NewProgram(vertexShader, fragmentShader ShaderSources) (Program, error)
|
||||
NewInputLayout(vertexShader ShaderSources, layout []InputDesc) (InputLayout, error)
|
||||
|
||||
@@ -43,17 +44,29 @@ type Device interface {
|
||||
BindTexture(unit int, t Texture)
|
||||
BindVertexBuffer(b Buffer, stride, offset int)
|
||||
BindIndexBuffer(b Buffer)
|
||||
BindImageTexture(unit int, texture Texture, access AccessBits, format TextureFormat)
|
||||
|
||||
MemoryBarrier()
|
||||
DispatchCompute(x, y, z int)
|
||||
}
|
||||
|
||||
type ShaderSources struct {
|
||||
GLSL100ES string
|
||||
GLSL300ES string
|
||||
GLSL130 string
|
||||
GLSL150 string
|
||||
HLSL []byte
|
||||
Uniforms UniformsReflection
|
||||
Inputs []InputLocation
|
||||
Textures []TextureBinding
|
||||
Name string
|
||||
GLSL100ES string
|
||||
GLSL300ES string
|
||||
GLSL310ES string
|
||||
GLSL130 string
|
||||
GLSL150 string
|
||||
HLSL []byte
|
||||
Uniforms UniformsReflection
|
||||
Inputs []InputLocation
|
||||
Textures []TextureBinding
|
||||
StorageBuffers []StorageBufferBinding
|
||||
}
|
||||
|
||||
type StorageBufferBinding struct {
|
||||
Binding int
|
||||
BlockSize int
|
||||
}
|
||||
|
||||
type UniformsReflection struct {
|
||||
@@ -105,6 +118,8 @@ type InputLayout interface {
|
||||
Release()
|
||||
}
|
||||
|
||||
type AccessBits uint8
|
||||
|
||||
type BlendFactor uint8
|
||||
|
||||
type DrawMode uint8
|
||||
@@ -127,6 +142,7 @@ type Caps struct {
|
||||
|
||||
type Program interface {
|
||||
Release()
|
||||
SetStorageBuffer(binding int, buf Buffer)
|
||||
SetVertexUniforms(buf Buffer)
|
||||
SetFragmentUniforms(buf Buffer)
|
||||
}
|
||||
@@ -134,6 +150,7 @@ type Program interface {
|
||||
type Buffer interface {
|
||||
Release()
|
||||
Upload(data []byte)
|
||||
Download(data []byte) error
|
||||
}
|
||||
|
||||
type Framebuffer interface {
|
||||
@@ -150,7 +167,7 @@ type Timer interface {
|
||||
}
|
||||
|
||||
type Texture interface {
|
||||
Upload(img *image.RGBA)
|
||||
Upload(offset, size image.Point, pixels []byte)
|
||||
Release()
|
||||
}
|
||||
|
||||
@@ -171,11 +188,18 @@ const (
|
||||
BufferBindingUniforms
|
||||
BufferBindingTexture
|
||||
BufferBindingFramebuffer
|
||||
BufferBindingShaderStorage
|
||||
)
|
||||
|
||||
const (
|
||||
TextureFormatSRGB TextureFormat = iota
|
||||
TextureFormatFloat
|
||||
TextureFormatRGBA8
|
||||
)
|
||||
|
||||
const (
|
||||
AccessRead AccessBits = 1 + iota
|
||||
AccessWrite
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -184,7 +208,9 @@ const (
|
||||
)
|
||||
|
||||
const (
|
||||
FeatureTimers Features = iota
|
||||
FeatureTimers Features = 1 << iota
|
||||
FeatureFloatRenderTargets
|
||||
FeatureCompute
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -202,3 +228,15 @@ const (
|
||||
func (f Features) Has(feats Features) bool {
|
||||
return f&feats == feats
|
||||
}
|
||||
|
||||
func UploadImage(t Texture, offset image.Point, img *image.RGBA) {
|
||||
var pixels []byte
|
||||
size := img.Bounds().Size()
|
||||
if img.Stride != size.X*4 {
|
||||
panic("unsupported stride")
|
||||
}
|
||||
start := img.PixOffset(0, 0)
|
||||
end := img.PixOffset(size.X, size.Y-1)
|
||||
pixels = img.Pix[start:end]
|
||||
t.Upload(offset, size, pixels)
|
||||
}
|
||||
|
||||
+127
-24
@@ -37,7 +37,7 @@ type glstate struct {
|
||||
// nattr is the current number of enabled vertex arrays.
|
||||
nattr int
|
||||
prog *gpuProgram
|
||||
texUnits [2]*gpuTexture
|
||||
texUnits [4]*gpuTexture
|
||||
layout *gpuInputLayout
|
||||
buffer bufferBinding
|
||||
}
|
||||
@@ -87,6 +87,7 @@ type gpuProgram struct {
|
||||
nattr int
|
||||
vertUniforms uniformsTracker
|
||||
fragUniforms uniformsTracker
|
||||
storage [storageBindings]*gpuBuffer
|
||||
}
|
||||
|
||||
type uniformsTracker struct {
|
||||
@@ -111,13 +112,17 @@ type gpuInputLayout struct {
|
||||
// textureTriple holds the type settings for
|
||||
// a TexImage2D call.
|
||||
type textureTriple struct {
|
||||
internalFormat int
|
||||
internalFormat glimpl.Enum
|
||||
format glimpl.Enum
|
||||
typ glimpl.Enum
|
||||
}
|
||||
|
||||
type Context = glimpl.Context
|
||||
|
||||
const (
|
||||
storageBindings = 32
|
||||
)
|
||||
|
||||
// NewBackend returns a new Backend.
|
||||
//
|
||||
// Pass a WebGL context if GOOS is "js", otherwise pass nil for the current
|
||||
@@ -133,24 +138,28 @@ func NewBackend(ctx Context) (*Backend, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
floatTriple, err := floatTripleFor(f, ver, exts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
floatTriple, ffboErr := floatTripleFor(f, ver, exts)
|
||||
srgbaTriple, err := srgbaTripleFor(ver, exts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ubo := ver[0] >= 3 && gles
|
||||
gles30 := gles && ver[0] >= 3
|
||||
gles31 := gles && (ver[0] > 3 || (ver[0] == 3 && ver[1] >= 1))
|
||||
b := &Backend{
|
||||
glver: ver,
|
||||
gles: gles,
|
||||
ubo: ubo,
|
||||
ubo: gles30,
|
||||
funcs: f,
|
||||
floatTriple: floatTriple,
|
||||
alphaTriple: alphaTripleFor(ver),
|
||||
srgbaTriple: srgbaTriple,
|
||||
}
|
||||
if ffboErr == nil {
|
||||
b.feats.Features |= backend.FeatureFloatRenderTargets
|
||||
}
|
||||
if gles31 {
|
||||
b.feats.Features |= backend.FeatureCompute
|
||||
}
|
||||
if hasExtension(exts, "GL_EXT_disjoint_timer_query_webgl2") || hasExtension(exts, "GL_EXT_disjoint_timer_query") {
|
||||
b.feats.Features |= backend.FeatureTimers
|
||||
}
|
||||
@@ -232,6 +241,8 @@ func (b *Backend) NewTexture(format backend.TextureFormat, width, height int, mi
|
||||
tex.triple = b.floatTriple
|
||||
case backend.TextureFormatSRGB:
|
||||
tex.triple = b.srgbaTriple
|
||||
case backend.TextureFormatRGBA8:
|
||||
tex.triple = textureTriple{glimpl.RGBA8, glimpl.RGBA, glimpl.UNSIGNED_BYTE}
|
||||
default:
|
||||
return nil, errors.New("unsupported texture format")
|
||||
}
|
||||
@@ -240,7 +251,12 @@ func (b *Backend) NewTexture(format backend.TextureFormat, width, height int, mi
|
||||
b.funcs.TexParameteri(glimpl.TEXTURE_2D, glimpl.TEXTURE_MIN_FILTER, toTexFilter(minFilter))
|
||||
b.funcs.TexParameteri(glimpl.TEXTURE_2D, glimpl.TEXTURE_WRAP_S, glimpl.CLAMP_TO_EDGE)
|
||||
b.funcs.TexParameteri(glimpl.TEXTURE_2D, glimpl.TEXTURE_WRAP_T, glimpl.CLAMP_TO_EDGE)
|
||||
b.funcs.TexImage2D(glimpl.TEXTURE_2D, 0, tex.triple.internalFormat, width, height, tex.triple.format, tex.triple.typ, nil)
|
||||
if b.gles && b.glver[0] >= 3 {
|
||||
// Immutable textures are required for BindImageTexture, and can't hurt otherwise.
|
||||
b.funcs.TexStorage2D(glimpl.TEXTURE_2D, 1, tex.triple.internalFormat, width, height)
|
||||
} else {
|
||||
b.funcs.TexImage2D(glimpl.TEXTURE_2D, 0, tex.triple.internalFormat, width, height, tex.triple.format, tex.triple.typ)
|
||||
}
|
||||
if err := glErr(b.funcs); err != nil {
|
||||
tex.Release()
|
||||
return nil, err
|
||||
@@ -267,6 +283,9 @@ func (b *Backend) NewBuffer(typ backend.BufferBinding, size int) (backend.Buffer
|
||||
buf.Release()
|
||||
return nil, err
|
||||
}
|
||||
firstBinding := firstBufferType(typ)
|
||||
b.funcs.BindBuffer(firstBinding, buf.obj)
|
||||
b.funcs.BufferData(firstBinding, size, glimpl.DYNAMIC_DRAW)
|
||||
}
|
||||
return buf, nil
|
||||
}
|
||||
@@ -275,6 +294,9 @@ func (b *Backend) NewImmutableBuffer(typ backend.BufferBinding, data []byte) (ba
|
||||
glErr(b.funcs)
|
||||
obj := b.funcs.CreateBuffer()
|
||||
buf := &gpuBuffer{backend: b, obj: obj, typ: typ, size: len(data), hasBuffer: true}
|
||||
firstBinding := firstBufferType(typ)
|
||||
b.funcs.BindBuffer(firstBinding, buf.obj)
|
||||
b.funcs.BufferData(firstBinding, len(data), glimpl.STATIC_DRAW)
|
||||
buf.Upload(data)
|
||||
buf.immutable = true
|
||||
if err := glErr(b.funcs); err != nil {
|
||||
@@ -291,6 +313,40 @@ func glErr(f *glimpl.Functions) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Backend) MemoryBarrier() {
|
||||
b.funcs.MemoryBarrier(glimpl.ALL_BARRIER_BITS)
|
||||
}
|
||||
|
||||
func (b *Backend) DispatchCompute(x, y, z int) {
|
||||
if p := b.state.prog; p != nil {
|
||||
for binding, buf := range p.storage {
|
||||
if buf != nil {
|
||||
b.funcs.BindBufferBase(glimpl.SHADER_STORAGE_BUFFER, binding, buf.obj)
|
||||
}
|
||||
}
|
||||
}
|
||||
b.funcs.DispatchCompute(x, y, z)
|
||||
}
|
||||
|
||||
func (b *Backend) BindImageTexture(unit int, tex backend.Texture, access backend.AccessBits, f backend.TextureFormat) {
|
||||
t := tex.(*gpuTexture)
|
||||
var acc glimpl.Enum
|
||||
switch access {
|
||||
case backend.AccessWrite:
|
||||
acc = glimpl.WRITE_ONLY
|
||||
default:
|
||||
panic("unsupported access bits")
|
||||
}
|
||||
var format glimpl.Enum
|
||||
switch f {
|
||||
case backend.TextureFormatRGBA8:
|
||||
format = glimpl.RGBA8
|
||||
default:
|
||||
panic("unsupported format")
|
||||
}
|
||||
b.funcs.BindImageTexture(unit, t.obj, 0, false, 0, acc, format)
|
||||
}
|
||||
|
||||
func (b *Backend) bindTexture(unit int, t *gpuTexture) {
|
||||
if b.state.texUnits[unit] != t {
|
||||
b.funcs.ActiveTexture(glimpl.TEXTURE0 + glimpl.Enum(unit))
|
||||
@@ -433,6 +489,18 @@ func (b *Backend) NewInputLayout(vs backend.ShaderSources, layout []backend.Inpu
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (b *Backend) NewComputeProgram(src backend.ShaderSources) (backend.Program, error) {
|
||||
p, err := glimpl.CreateComputeProgram(b.funcs, src.GLSL310ES)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: %v", src.Name, err)
|
||||
}
|
||||
gpuProg := &gpuProgram{
|
||||
backend: b,
|
||||
obj: p,
|
||||
}
|
||||
return gpuProg, nil
|
||||
}
|
||||
|
||||
func (b *Backend) NewProgram(vertShader, fragShader backend.ShaderSources) (backend.Program, error) {
|
||||
attr := make([]string, len(vertShader.Inputs))
|
||||
for _, inp := range vertShader.Inputs {
|
||||
@@ -503,6 +571,14 @@ func lookupUniform(funcs *glimpl.Functions, p glimpl.Program, loc backend.Unifor
|
||||
return uniformLocation{uniform: u, offset: loc.Offset, typ: loc.Type, size: loc.Size}
|
||||
}
|
||||
|
||||
func (p *gpuProgram) SetStorageBuffer(binding int, buffer backend.Buffer) {
|
||||
buf := buffer.(*gpuBuffer)
|
||||
if buf.typ&backend.BufferBindingShaderStorage == 0 {
|
||||
panic("not a shader storage buffer")
|
||||
}
|
||||
p.storage[binding] = buf
|
||||
}
|
||||
|
||||
func (p *gpuProgram) SetVertexUniforms(buffer backend.Buffer) {
|
||||
p.vertUniforms.setBuffer(buffer)
|
||||
}
|
||||
@@ -600,10 +676,31 @@ func (b *gpuBuffer) Upload(data []byte) {
|
||||
if b.hasBuffer {
|
||||
firstBinding := firstBufferType(b.typ)
|
||||
b.backend.funcs.BindBuffer(firstBinding, b.obj)
|
||||
b.backend.funcs.BufferData(firstBinding, data, glimpl.STATIC_DRAW)
|
||||
b.backend.funcs.BufferSubData(firstBinding, 0, data)
|
||||
}
|
||||
}
|
||||
|
||||
func (b *gpuBuffer) Download(data []byte) error {
|
||||
if len(data) > b.size {
|
||||
panic("buffer size overflow")
|
||||
}
|
||||
if !b.hasBuffer {
|
||||
copy(data, b.data)
|
||||
return nil
|
||||
}
|
||||
firstBinding := firstBufferType(b.typ)
|
||||
b.backend.funcs.BindBuffer(firstBinding, b.obj)
|
||||
bufferMap := b.backend.funcs.MapBufferRange(firstBinding, 0, len(data), glimpl.MAP_READ_BIT)
|
||||
if bufferMap == nil {
|
||||
return fmt.Errorf("MapBufferRange: error %#x", b.backend.funcs.GetError())
|
||||
}
|
||||
copy(data, bufferMap)
|
||||
if !b.backend.funcs.UnmapBuffer(firstBinding) {
|
||||
return errors.New("buffer content lost")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *gpuBuffer) Release() {
|
||||
if b.hasBuffer {
|
||||
b.backend.funcs.DeleteBuffer(b.obj)
|
||||
@@ -649,10 +746,20 @@ func (b *Backend) BindIndexBuffer(buf backend.Buffer) {
|
||||
b.funcs.BindBuffer(glimpl.ELEMENT_ARRAY_BUFFER, gbuf.obj)
|
||||
}
|
||||
|
||||
func (b *Backend) BlitFramebuffer(dst, src backend.Framebuffer, srect, drect image.Rectangle) {
|
||||
b.funcs.BindFramebuffer(glimpl.DRAW_FRAMEBUFFER, dst.(*gpuFramebuffer).obj)
|
||||
b.funcs.BindFramebuffer(glimpl.READ_FRAMEBUFFER, src.(*gpuFramebuffer).obj)
|
||||
b.funcs.BlitFramebuffer(
|
||||
srect.Min.X, srect.Min.Y, srect.Max.X, srect.Max.Y,
|
||||
drect.Min.X, drect.Min.Y, drect.Max.X, drect.Max.Y,
|
||||
glimpl.COLOR_BUFFER_BIT|glimpl.DEPTH_BUFFER_BIT|glimpl.STENCIL_BUFFER_BIT,
|
||||
glimpl.NEAREST)
|
||||
}
|
||||
|
||||
func (f *gpuFramebuffer) ReadPixels(src image.Rectangle, pixels []byte) error {
|
||||
glErr(f.backend.funcs)
|
||||
f.backend.BindFramebuffer(f)
|
||||
if len(pixels) < src.Dx()*src.Dy() {
|
||||
if len(pixels) < src.Dx()*src.Dy()*4 {
|
||||
return errors.New("unexpected RGBA size")
|
||||
}
|
||||
f.backend.funcs.ReadPixels(src.Min.X, src.Min.Y, src.Dx(), src.Dy(), glimpl.RGBA, glimpl.UNSIGNED_BYTE, pixels)
|
||||
@@ -714,18 +821,12 @@ func (t *gpuTexture) Release() {
|
||||
t.backend.funcs.DeleteTexture(t.obj)
|
||||
}
|
||||
|
||||
func (t *gpuTexture) Upload(img *image.RGBA) {
|
||||
t.backend.BindTexture(0, t)
|
||||
var pixels []byte
|
||||
b := img.Bounds()
|
||||
w, h := b.Dx(), b.Dy()
|
||||
if img.Stride != w*4 {
|
||||
panic("unsupported stride")
|
||||
func (t *gpuTexture) Upload(offset, size image.Point, pixels []byte) {
|
||||
if min := size.X * size.Y * 4; min > len(pixels) {
|
||||
panic(fmt.Errorf("size %d larger than data %d", min, len(pixels)))
|
||||
}
|
||||
start := (b.Min.X + b.Min.Y*w) * 4
|
||||
end := (b.Max.X + (b.Max.Y-1)*w) * 4
|
||||
pixels = img.Pix[start:end]
|
||||
t.backend.funcs.TexImage2D(glimpl.TEXTURE_2D, 0, t.triple.internalFormat, w, h, t.triple.format, t.triple.typ, pixels)
|
||||
t.backend.BindTexture(0, t)
|
||||
t.backend.funcs.TexSubImage2D(glimpl.TEXTURE_2D, 0, offset.X, offset.Y, size.X, size.Y, t.triple.format, t.triple.typ, pixels)
|
||||
}
|
||||
|
||||
func (t *gpuTimer) Begin() {
|
||||
@@ -792,7 +893,7 @@ func floatTripleFor(f *glimpl.Functions, ver [2]int, exts []string) (textureTrip
|
||||
var attempts []string
|
||||
for _, tt := range triples {
|
||||
const size = 256
|
||||
f.TexImage2D(glimpl.TEXTURE_2D, 0, tt.internalFormat, size, size, tt.format, tt.typ, nil)
|
||||
f.TexImage2D(glimpl.TEXTURE_2D, 0, tt.internalFormat, size, size, tt.format, tt.typ)
|
||||
f.FramebufferTexture2D(glimpl.FRAMEBUFFER, glimpl.COLOR_ATTACHMENT0, glimpl.TEXTURE_2D, tex, 0)
|
||||
st := f.CheckFramebufferStatus(glimpl.FRAMEBUFFER)
|
||||
if st == glimpl.FRAMEBUFFER_COMPLETE {
|
||||
@@ -815,7 +916,7 @@ func srgbaTripleFor(ver [2]int, exts []string) (textureTriple, error) {
|
||||
}
|
||||
|
||||
func alphaTripleFor(ver [2]int) textureTriple {
|
||||
intf, f := glimpl.R8, glimpl.Enum(glimpl.RED)
|
||||
intf, f := glimpl.Enum(glimpl.R8), glimpl.Enum(glimpl.RED)
|
||||
if ver[0] < 3 {
|
||||
// R8, RED not supported on OpenGL ES 2.0.
|
||||
intf, f = glimpl.LUMINANCE, glimpl.Enum(glimpl.LUMINANCE)
|
||||
@@ -840,6 +941,8 @@ func firstBufferType(typ backend.BufferBinding) glimpl.Enum {
|
||||
return glimpl.ARRAY_BUFFER
|
||||
case typ&backend.BufferBindingUniforms != 0:
|
||||
return glimpl.UNIFORM_BUFFER
|
||||
case typ&backend.BufferBindingShaderStorage != 0:
|
||||
return glimpl.SHADER_STORAGE_BUFFER
|
||||
default:
|
||||
panic("unsupported buffer type")
|
||||
}
|
||||
|
||||
+1
-1
@@ -479,7 +479,7 @@ func (r *renderer) texHandle(cache *resourceCache, data imageOpData) backend.Tex
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
handle.Upload(data.src)
|
||||
backend.UploadImage(handle, image.Pt(0, 0), data.src)
|
||||
tex.tex = handle
|
||||
return tex.tex
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user