Files
2026-09-26 16:32:40 +03:00

306 lines
7.2 KiB
Go

package gl33
import (
"strings"
"unsafe"
"aego/core/errs"
"aego/core/handle"
"aego/core/log"
"aego/gpu"
"aego/gpu/gl33/gl"
"aego/gpu/gl33/internal/glstate"
)
const scratchUnit = glstate.MaxTextureUnits - 1
type Options struct {
Debug bool
Log *log.Logger
}
type glBuffer struct {
id uint32
usage gpu.BufferUsage
size int
index gpu.IndexFormat
}
type glTexture struct {
id uint32
fbo uint32
w, h int
info texInfo
mips bool
}
type glSampler struct {
id uint32
}
type glShader struct {
program uint32
}
const _ = uint(glstate.MaxVertexSlots - gpu.MaxVertexSlots)
const _ = uint(gpu.MaxVertexSlots - glstate.MaxVertexSlots)
type glPipeline struct {
vao uint32
program uint32
prim gpu.Primitive
mode uint32
attrs []glstate.Attrib
used [gpu.MaxVertexSlots]bool
bind glstate.VertexBinding
blend glstate.Blend
depth glstate.Depth
cull glstate.Cull
}
type resourceKind uint8
const (
kindBuffer resourceKind = iota
kindTexture
kindSampler
kindShader
kindPipeline
kindQuery
)
type pendingDestroy struct {
kind resourceKind
index, gen uint32
}
type Device struct {
log *log.ChannelLogger
debug bool
caps gpu.Capabilities
api realAPI
cache *glstate.Cache
buffers *handle.Arena[glBuffer]
textures *handle.Arena[glTexture]
samplers *handle.Arena[glSampler]
shaders *handle.Arena[glShader]
pipelines *handle.Arena[glPipeline]
queries *handle.Arena[glQuery]
frame frame
pass pass
inFrame bool
activeQuery uint32
pending []pendingDestroy
fbW, fbH int
debugProc gl.DebugProc
}
func New(getProc func(string) unsafe.Pointer, opts Options) (*Device, error) {
if err := gl.InitWithProcAddrFunc(getProc); err != nil {
return nil, errs.Wrap(err, errs.GPUInit, "load OpenGL 3.3 core functions")
}
logger := opts.Log
if logger == nil {
logger = log.Nop()
}
d := &Device{
log: logger.With(log.GPU),
debug: opts.Debug,
api: realAPI{},
cache: glstate.New(realAPI{}),
buffers: newArena[glBuffer](logger, "buffer", 256),
textures: newArena[glTexture](logger, "texture", 128),
samplers: newArena[glSampler](logger, "sampler", 16),
shaders: newArena[glShader](logger, "shader", 32),
pipelines: newArena[glPipeline](logger, "pipeline", 32),
queries: newArena[glQuery](logger, "query", 16),
pending: make([]pendingDestroy, 0, 64),
}
d.frame.d = d
d.pass.d = d
d.queryCaps()
if d.debug && d.caps.DebugOutput {
d.installDebugOutput()
}
gl.Enable(gl.FRAMEBUFFER_SRGB)
gl.PixelStorei(gl.UNPACK_ALIGNMENT, 1)
return d, nil
}
func newArena[T any](l *log.Logger, kind string, capacity int) *handle.Arena[T] {
a := handle.NewArena[T](capacity)
cl := l.With(log.GPU)
a.OnGrow = func(from, to int) {
cl.Warn("resource arena grows", log.F("kind", kind), log.F("from", from), log.F("to", to))
}
return a
}
func (d *Device) Caps() gpu.Capabilities {
return d.caps
}
func (d *Device) queryCaps() {
c := &d.caps
c.Vendor = gl.GoStr(gl.GetString(gl.VENDOR))
c.Renderer = gl.GoStr(gl.GetString(gl.RENDERER))
c.Version = gl.GoStr(gl.GetString(gl.VERSION))
c.GLSL = gl.GoStr(gl.GetString(gl.SHADING_LANGUAGE_VERSION))
c.MaxTextureSize = getInt(gl.MAX_TEXTURE_SIZE)
c.MaxTextureUnits = min(getInt(gl.MAX_COMBINED_TEXTURE_IMAGE_UNITS), scratchUnit)
c.MaxSamples = getInt(gl.MAX_SAMPLES)
c.Instancing = true
c.TimerQueries = true
c.TextureArrays = true
major, minor := getInt(gl.MAJOR_VERSION), getInt(gl.MINOR_VERSION)
exts := extensions()
c.DebugOutput = exts["GL_KHR_debug"] || major > 4 || (major == 4 && minor >= 3)
c.SRGB = defaultFramebufferSRGB()
c.ClipZeroToOne = false
c.OriginBottomLeft = true
}
func getInt(pname uint32) int {
var v int32
gl.GetIntegerv(pname, &v)
return int(v)
}
func extensions() map[string]bool {
n := getInt(gl.NUM_EXTENSIONS)
out := make(map[string]bool, n)
for i := 0; i < n; i++ {
out[gl.GoStr(gl.GetStringi(gl.EXTENSIONS, uint32(i)))] = true
}
return out
}
func defaultFramebufferSRGB() bool {
var enc int32
gl.BindFramebuffer(gl.FRAMEBUFFER, 0)
gl.GetFramebufferAttachmentParameteriv(gl.FRAMEBUFFER, gl.BACK_LEFT, gl.FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING, &enc)
if gl.GetError() != gl.NO_ERROR {
gl.GetFramebufferAttachmentParameteriv(gl.FRAMEBUFFER, gl.BACK, gl.FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING, &enc)
for gl.GetError() != gl.NO_ERROR {
}
}
return enc == gl.SRGB
}
func (d *Device) installDebugOutput() {
logger := d.log.Logger()
d.debugProc = func(source, gltype, id, severity uint32, length int32, message string, userParam unsafe.Pointer) {
lv := log.Debug
switch severity {
case gl.DEBUG_SEVERITY_HIGH:
lv = log.Error
case gl.DEBUG_SEVERITY_MEDIUM:
lv = log.Warn
case gl.DEBUG_SEVERITY_LOW:
lv = log.Info
}
if !logger.Enabled(lv) {
return
}
logger.Log(log.GPU, lv, strings.TrimSpace(message),
log.F("id", id),
log.F("type", debugTypeName(gltype)),
log.F("source", debugSourceName(source)))
}
gl.Enable(gl.DEBUG_OUTPUT)
gl.Enable(gl.DEBUG_OUTPUT_SYNCHRONOUS)
gl.DebugMessageCallback(d.debugProc, nil)
gl.DebugMessageControl(gl.DONT_CARE, gl.DONT_CARE, gl.DEBUG_SEVERITY_NOTIFICATION, 0, nil, false)
d.log.Debug("KHR_debug output enabled")
}
func (d *Device) label(kind, id uint32, label string) {
if !d.debug || !d.caps.DebugOutput || label == "" {
return
}
cs, free := gl.Strs(label + "\x00")
gl.ObjectLabel(kind, id, int32(len(label)), *cs)
free()
}
func (d *Device) checkError(op, label string) error {
if !d.debug {
return nil
}
var codes []string
for i := 0; i < 8; i++ {
e := gl.GetError()
if e == gl.NO_ERROR {
break
}
codes = append(codes, glErrorName(e))
}
if len(codes) == 0 {
return nil
}
return errs.New(errs.GPUResource, op+" failed",
log.F("label", label), log.F("gl", strings.Join(codes, ",")))
}
func (d *Device) BeginFrame(pixelW, pixelH int) gpu.Frame {
d.fbW, d.fbH = pixelW, pixelH
d.frame.stats = gpu.FrameStats{}
d.inFrame = true
return &d.frame
}
func (d *Device) EndFrame() {
if d.pass.active {
d.log.Warn("render pass was not ended before EndFrame")
d.pass.active = false
}
d.inFrame = false
for _, p := range d.pending {
d.destroyNow(p.kind, p.index, p.gen)
}
d.pending = d.pending[:0]
}
func (d *Device) Close() {
d.pending = d.pending[:0]
d.inFrame = false
d.cache.BindVertexArray(0)
d.cache.UseProgram(0)
d.cache.BindFramebuffer(0)
d.pipelines.Each(func(_ handle.Handle[glPipeline], p *glPipeline) {
id := p.vao
gl.DeleteVertexArrays(1, &id)
})
d.shaders.Each(func(_ handle.Handle[glShader], s *glShader) {
gl.DeleteProgram(s.program)
})
d.samplers.Each(func(_ handle.Handle[glSampler], s *glSampler) {
id := s.id
gl.DeleteSamplers(1, &id)
})
d.textures.Each(func(_ handle.Handle[glTexture], t *glTexture) {
if t.fbo != 0 {
fbo := t.fbo
gl.DeleteFramebuffers(1, &fbo)
}
id := t.id
gl.DeleteTextures(1, &id)
})
d.buffers.Each(func(_ handle.Handle[glBuffer], b *glBuffer) {
id := b.id
gl.DeleteBuffers(1, &id)
})
d.queries.Each(func(_ handle.Handle[glQuery], q *glQuery) {
id := q.id
gl.DeleteQueries(1, &id)
})
d.pipelines.Clear()
d.shaders.Clear()
d.samplers.Clear()
d.textures.Clear()
d.buffers.Clear()
d.queries.Clear()
d.activeQuery = 0
d.cache.Invalidate()
}