Clean SDL/OpenGL functions

This commit is contained in:
sergystepanov 2025-12-27 01:58:14 +03:00
parent 8754a5edfa
commit 58a19affcb
3 changed files with 135 additions and 203 deletions

View file

@ -9,24 +9,6 @@ import (
"github.com/giongto35/cloud-game/v3/pkg/worker/caged/libretro/graphics/gl"
)
type (
offscreenSetup struct {
tex uint32
fbo uint32
rbo uint32
width int32
height int32
pixType uint32
pixFormat uint32
hasDepth bool
hasStencil bool
}
PixelFormat int
)
type Context int
const (
@ -37,11 +19,12 @@ const (
CtxOpenGlEs3
CtxOpenGlEsVersion
CtxVulkan
CtxUnknown = math.MaxInt32 - 1
CtxDummy = math.MaxInt32
)
type PixelFormat int
const (
UnsignedShort5551 PixelFormat = iota
UnsignedShort565
@ -49,99 +32,91 @@ const (
)
var (
opt = offscreenSetup{}
buf = make([]byte, 1024*1024)
fbo, tex, rbo uint32
hasDepth bool
pixType, pixFormat uint32
buf []byte
bufPtr unsafe.Pointer
)
func initContext(getProcAddr func(name string) unsafe.Pointer) {
if err := gl.InitWithProcAddrFunc(getProcAddr); err != nil {
panic(err)
}
gl.PixelStorei(gl.PackAlignment, 1)
}
func initFramebuffer(w int, h int, hasDepth bool, hasStencil bool) error {
opt.width = int32(w)
opt.height = int32(h)
opt.hasDepth = hasDepth
opt.hasStencil = hasStencil
// texture init
gl.GenTextures(1, &opt.tex)
gl.BindTexture(gl.Texture2d, opt.tex)
func initFramebuffer(width, height int, depth, stencil bool) error {
w, h := int32(width), int32(height)
hasDepth = depth
gl.GenTextures(1, &tex)
gl.BindTexture(gl.Texture2d, tex)
gl.TexParameteri(gl.Texture2d, gl.TextureMinFilter, gl.NEAREST)
gl.TexParameteri(gl.Texture2d, gl.TextureMagFilter, gl.NEAREST)
gl.TexImage2D(gl.Texture2d, 0, gl.RGBA8, opt.width, opt.height, 0, opt.pixType, opt.pixFormat, nil)
gl.TexImage2D(gl.Texture2d, 0, gl.RGBA8, w, h, 0, pixType, pixFormat, nil)
gl.BindTexture(gl.Texture2d, 0)
// framebuffer init
gl.GenFramebuffers(1, &opt.fbo)
gl.BindFramebuffer(gl.FRAMEBUFFER, opt.fbo)
gl.GenFramebuffers(1, &fbo)
gl.BindFramebuffer(gl.FRAMEBUFFER, fbo)
gl.FramebufferTexture2D(gl.FRAMEBUFFER, gl.ColorAttachment0, gl.Texture2d, tex, 0)
gl.FramebufferTexture2D(gl.FRAMEBUFFER, gl.ColorAttachment0, gl.Texture2d, opt.tex, 0)
// more buffers init
if opt.hasDepth {
gl.GenRenderbuffers(1, &opt.rbo)
gl.BindRenderbuffer(gl.RENDERBUFFER, opt.rbo)
if opt.hasStencil {
gl.RenderbufferStorage(gl.RENDERBUFFER, gl.Depth24Stencil8, opt.width, opt.height)
gl.FramebufferRenderbuffer(gl.FRAMEBUFFER, gl.DepthStencilAttachment, gl.RENDERBUFFER, opt.rbo)
} else {
gl.RenderbufferStorage(gl.RENDERBUFFER, gl.DepthComponent24, opt.width, opt.height)
gl.FramebufferRenderbuffer(gl.FRAMEBUFFER, gl.DepthAttachment, gl.RENDERBUFFER, opt.rbo)
if depth {
gl.GenRenderbuffers(1, &rbo)
gl.BindRenderbuffer(gl.RENDERBUFFER, rbo)
format, attachment := uint32(gl.DepthComponent24), uint32(gl.DepthAttachment)
if stencil {
format, attachment = gl.Depth24Stencil8, gl.DepthStencilAttachment
}
gl.RenderbufferStorage(gl.RENDERBUFFER, format, w, h)
gl.FramebufferRenderbuffer(gl.FRAMEBUFFER, attachment, gl.RENDERBUFFER, rbo)
gl.BindRenderbuffer(gl.RENDERBUFFER, 0)
}
if status := gl.CheckFramebufferStatus(gl.FRAMEBUFFER); status != gl.FramebufferComplete {
return fmt.Errorf("invalid framebuffer (0x%X)", status)
return fmt.Errorf("framebuffer incomplete: 0x%X", status)
}
return nil
}
func destroyFramebuffer() {
if opt.hasDepth {
gl.DeleteRenderbuffers(1, &opt.rbo)
if hasDepth {
gl.DeleteRenderbuffers(1, &rbo)
}
gl.DeleteFramebuffers(1, &opt.fbo)
gl.DeleteTextures(1, &opt.tex)
gl.DeleteFramebuffers(1, &fbo)
gl.DeleteTextures(1, &tex)
}
func ReadFramebuffer(bytes, w, h uint) []byte {
data := buf[:bytes:bytes]
gl.PixelStorei(gl.PackAlignment, 1)
gl.BindFramebuffer(gl.FRAMEBUFFER, opt.fbo)
gl.ReadPixels(0, 0, int32(w), int32(h), opt.pixType, opt.pixFormat, unsafe.Pointer(&data[0]))
return data
func ReadFramebuffer(size, w, h uint) []byte {
gl.BindFramebuffer(gl.FRAMEBUFFER, fbo)
gl.ReadPixels(0, 0, int32(w), int32(h), pixType, pixFormat, bufPtr)
return buf[:size]
}
func getFbo() uint32 { return opt.fbo }
func SetBuffer(size int) { buf = make([]byte, size) }
func SetBuffer(size int) {
buf = make([]byte, size)
bufPtr = unsafe.Pointer(&buf[0])
}
func SetPixelFormat(format PixelFormat) error {
switch format {
case UnsignedShort5551:
opt.pixFormat = gl.UnsignedShort5551
opt.pixType = gl.BGRA
pixFormat, pixType = gl.UnsignedShort5551, gl.BGRA
case UnsignedShort565:
opt.pixFormat = gl.UnsignedShort565
opt.pixType = gl.RGB
pixFormat, pixType = gl.UnsignedShort565, gl.RGB
case UnsignedInt8888Rev:
opt.pixFormat = gl.UnsignedInt8888Rev
opt.pixType = gl.BGRA
pixFormat, pixType = gl.UnsignedInt8888Rev, gl.BGRA
default:
return errors.New("unknown pixel format")
}
return nil
}
func GetGLVersionInfo() string { return get(gl.VERSION) }
func GetGLVendorInfo() string { return get(gl.VENDOR) }
func GetGLRendererInfo() string { return get(gl.RENDERER) }
func GetGLSLInfo() string { return get(gl.ShadingLanguageVersion) }
func GetGLError() uint32 { return gl.GetError() }
func GLInfo() (version, vendor, renderer, glsl string) {
return gl.GoStr(gl.GetString(gl.VERSION)),
gl.GoStr(gl.GetString(gl.VENDOR)),
gl.GoStr(gl.GetString(gl.RENDERER)),
gl.GoStr(gl.GetString(gl.ShadingLanguageVersion))
}
func get(name uint32) string { return gl.GoStr(gl.GetString(name)) }
func GlFbo() uint32 { return fbo }

View file

@ -4,21 +4,17 @@ import (
"fmt"
"unsafe"
"github.com/giongto35/cloud-game/v3/pkg/logger"
"github.com/giongto35/cloud-game/v3/pkg/worker/thread"
"github.com/veandco/go-sdl2/sdl"
)
type SDL struct {
glWCtx sdl.GLContext
w *sdl.Window
log *logger.Logger
w *sdl.Window
ctx sdl.GLContext
}
type Config struct {
Ctx Context
W int
H int
W, H int
GLAutoContext bool
GLVersionMajor uint
GLVersionMinor uint
@ -26,123 +22,79 @@ type Config struct {
GLHasStencil bool
}
// NewSDLContext initializes SDL/OpenGL context.
// Uses main thread lock (see thread/mainthread).
func NewSDLContext(cfg Config, log *logger.Logger) (*SDL, error) {
log.Debug().Msg("[SDL/OpenGL] initialization...")
func NewSDLContext(cfg Config) (*SDL, error) {
if err := sdl.Init(sdl.INIT_VIDEO); err != nil {
return nil, fmt.Errorf("SDL initialization fail: %w", err)
return nil, fmt.Errorf("sdl: %w", err)
}
display := SDL{log: log}
if cfg.GLAutoContext {
log.Debug().Msgf("[OpenGL] CONTEXT_AUTO (type: %v v%v.%v)", cfg.Ctx, cfg.GLVersionMajor, cfg.GLVersionMinor)
} else {
switch cfg.Ctx {
case CtxOpenGlCore:
display.setAttribute(sdl.GL_CONTEXT_PROFILE_MASK, sdl.GL_CONTEXT_PROFILE_CORE)
log.Debug().Msgf("[OpenGL] CONTEXT_PROFILE_CORE")
case CtxOpenGlEs2:
display.setAttribute(sdl.GL_CONTEXT_PROFILE_MASK, sdl.GL_CONTEXT_PROFILE_ES)
display.setAttribute(sdl.GL_CONTEXT_MAJOR_VERSION, 3)
display.setAttribute(sdl.GL_CONTEXT_MINOR_VERSION, 0)
log.Debug().Msgf("[OpenGL] CONTEXT_PROFILE_ES 3.0")
case CtxOpenGl:
if cfg.GLVersionMajor >= 3 {
display.setAttribute(sdl.GL_CONTEXT_PROFILE_MASK, sdl.GL_CONTEXT_PROFILE_COMPATIBILITY)
}
log.Debug().Msgf("[OpenGL] CONTEXT_PROFILE_COMPATIBILITY")
default:
log.Error().Msgf("[OpenGL] Unsupported hw context: %v", cfg.Ctx)
if !cfg.GLAutoContext {
if err := setGLAttrs(cfg.Ctx); err != nil {
return nil, err
}
}
var err error
// In OSX 10.14+ window creation and context creation must happen in the main thread
thread.Main(func() { display.w, display.glWCtx, err = createWindow() })
w, err := sdl.CreateWindow("cloud-retro", sdl.WINDOWPOS_UNDEFINED, sdl.WINDOWPOS_UNDEFINED, 1, 1, sdl.WINDOW_OPENGL|sdl.WINDOW_HIDDEN)
if err != nil {
return nil, fmt.Errorf("window fail: %w", err)
return nil, fmt.Errorf("window: %w", err)
}
if err := display.BindContext(); err != nil {
return nil, fmt.Errorf("bind context fail: %w", err)
ctx, err := w.GLCreateContext()
if err != nil {
err1 := w.Destroy()
return nil, fmt.Errorf("gl context: %w, destroy err: %w", err, err1)
}
if err = w.GLMakeCurrent(ctx); err != nil {
return nil, fmt.Errorf("gl bind: %w", err)
}
initContext(sdl.GLGetProcAddress)
if err := initFramebuffer(cfg.W, cfg.H, cfg.GLHasDepth, cfg.GLHasStencil); err != nil {
return nil, fmt.Errorf("OpenGL initialization fail: %w", err)
if err = initFramebuffer(cfg.W, cfg.H, cfg.GLHasDepth, cfg.GLHasStencil); err != nil {
return nil, fmt.Errorf("fbo: %w", err)
}
return &display, nil
return &SDL{w: w, ctx: ctx}, nil
}
// TryInit check weather SDL context can be created on the system.
func setGLAttrs(ctx Context) error {
set := sdl.GLSetAttribute
switch ctx {
case CtxOpenGlCore:
return set(sdl.GL_CONTEXT_PROFILE_MASK, sdl.GL_CONTEXT_PROFILE_CORE)
case CtxOpenGlEs2:
for _, a := range [][2]int{
{sdl.GL_CONTEXT_PROFILE_MASK, sdl.GL_CONTEXT_PROFILE_ES},
{sdl.GL_CONTEXT_MAJOR_VERSION, 3},
{sdl.GL_CONTEXT_MINOR_VERSION, 0},
} {
if err := set(sdl.GLattr(a[0]), a[1]); err != nil {
return err
}
}
return nil
case CtxOpenGl:
return set(sdl.GL_CONTEXT_PROFILE_MASK, sdl.GL_CONTEXT_PROFILE_COMPATIBILITY)
default:
return fmt.Errorf("unsupported gl context: %v", ctx)
}
}
func (s *SDL) Deinit() error {
destroyFramebuffer()
sdl.GLDeleteContext(s.ctx)
err := s.w.Destroy()
sdl.Quit()
return err
}
func (s *SDL) BindContext() error { return s.w.GLMakeCurrent(s.ctx) }
func GlProcAddress(proc string) unsafe.Pointer { return sdl.GLGetProcAddress(proc) }
func TryInit() error {
if err := sdl.Init(sdl.INIT_VIDEO); err != nil {
return fmt.Errorf("SDL init fail: %w", err)
}
sdl.Quit()
return nil
}
// Deinit destroys SDL/OpenGL context.
// Uses main thread lock (see thread/mainthread).
func (s *SDL) Deinit() error {
s.log.Debug().Msg("[SDL/OpenGL] shutdown...")
destroyFramebuffer()
var err error
// In OSX 10.14+ window deletion must happen in the main thread
thread.Main(func() {
err = s.destroyWindow()
})
if err != nil {
return fmt.Errorf("[SDL/OpenGL] deinit fail: %w", err)
}
sdl.Quit()
s.log.Debug().Msgf("[SDL/OpenGL] shutdown codes:(%v, %v)", sdl.GetError(), GetGLError())
return nil
}
// createWindow creates a fake SDL window just for OpenGL initialization purposes.
func createWindow() (*sdl.Window, sdl.GLContext, error) {
w, err := sdl.CreateWindow(
"CloudRetro dummy window",
sdl.WINDOWPOS_UNDEFINED, sdl.WINDOWPOS_UNDEFINED,
1, 1,
sdl.WINDOW_OPENGL|sdl.WINDOW_HIDDEN,
)
if err != nil {
return nil, nil, fmt.Errorf("window creation fail: %w", err)
}
glWCtx, err := w.GLCreateContext()
if err != nil {
return nil, nil, fmt.Errorf("window OpenGL context fail: %w", err)
}
return w, glWCtx, nil
}
// destroyWindow destroys previously created SDL window.
func (s *SDL) destroyWindow() error {
if err := s.BindContext(); err != nil {
return err
}
sdl.GLDeleteContext(s.glWCtx)
if err := s.w.Destroy(); err != nil {
return fmt.Errorf("window destroy fail: %w", err)
}
sdl.Quit()
return nil
}
// BindContext explicitly binds context to current thread.
func (s *SDL) BindContext() error { return s.w.GLMakeCurrent(s.glWCtx) }
// setAttribute tries to set a GL attribute or prints error.
func (s *SDL) setAttribute(attr sdl.GLattr, value int) {
if err := sdl.GLSetAttribute(attr, value); err != nil {
s.log.Error().Err(err).Msg("[SDL] attribute")
}
}
func GetGlFbo() uint32 { return getFbo() }
func GetGlProcAddress(proc string) unsafe.Pointer { return sdl.GLGetProcAddress(proc) }

View file

@ -486,10 +486,11 @@ func setRotation(rot uint) {
func printOpenGLDriverInfo() {
var openGLInfo strings.Builder
openGLInfo.Grow(128)
openGLInfo.WriteString(fmt.Sprintf("\n[OpenGL] Version: %v\n", graphics.GetGLVersionInfo()))
openGLInfo.WriteString(fmt.Sprintf("[OpenGL] Vendor: %v\n", graphics.GetGLVendorInfo()))
openGLInfo.WriteString(fmt.Sprintf("[OpenGL] Renderer: %v\n", graphics.GetGLRendererInfo()))
openGLInfo.WriteString(fmt.Sprintf("[OpenGL] GLSL Version: %v", graphics.GetGLSLInfo()))
version, vendor, renderrer, glsl := graphics.GLInfo()
openGLInfo.WriteString(fmt.Sprintf("\n[OpenGL] Version: %v\n", version))
openGLInfo.WriteString(fmt.Sprintf("[OpenGL] Vendor: %v\n", vendor))
openGLInfo.WriteString(fmt.Sprintf("[OpenGL] Renderer: %v\n", renderrer))
openGLInfo.WriteString(fmt.Sprintf("[OpenGL] GLSL Version: %v", glsl))
Nan0.log.Debug().Msg(openGLInfo.String())
}
@ -711,11 +712,11 @@ func coreLog(level C.enum_retro_log_level, msg *C.char) {
}
//export coreGetCurrentFramebuffer
func coreGetCurrentFramebuffer() C.uintptr_t { return (C.uintptr_t)(graphics.GetGlFbo()) }
func coreGetCurrentFramebuffer() C.uintptr_t { return (C.uintptr_t)(graphics.GlFbo()) }
//export coreGetProcAddress
func coreGetProcAddress(sym *C.char) C.retro_proc_address_t {
return (C.retro_proc_address_t)(graphics.GetGlProcAddress(C.GoString(sym)))
return (C.retro_proc_address_t)(graphics.GlProcAddress(C.GoString(sym)))
}
//export coreEnvironment
@ -857,20 +858,22 @@ func initVideo() {
context = graphics.CtxUnknown
}
sdl, err := graphics.NewSDLContext(graphics.Config{
Ctx: context,
W: int(Nan0.sys.av.geometry.max_width),
H: int(Nan0.sys.av.geometry.max_height),
GLAutoContext: Nan0.Video.gl.autoCtx,
GLVersionMajor: uint(Nan0.Video.hw.version_major),
GLVersionMinor: uint(Nan0.Video.hw.version_minor),
GLHasDepth: bool(Nan0.Video.hw.depth),
GLHasStencil: bool(Nan0.Video.hw.stencil),
}, Nan0.log)
if err != nil {
panic(err)
}
Nan0.sdlCtx = sdl
thread.Main(func() {
var err error
Nan0.sdlCtx, err = graphics.NewSDLContext(graphics.Config{
Ctx: context,
W: int(Nan0.sys.av.geometry.max_width),
H: int(Nan0.sys.av.geometry.max_height),
GLAutoContext: Nan0.Video.gl.autoCtx,
GLVersionMajor: uint(Nan0.Video.hw.version_major),
GLVersionMinor: uint(Nan0.Video.hw.version_minor),
GLHasDepth: bool(Nan0.Video.hw.depth),
GLHasStencil: bool(Nan0.Video.hw.stencil),
})
if err != nil {
panic(err)
}
})
if Nan0.log.GetLevel() < logger.InfoLevel {
printOpenGLDriverInfo()
@ -882,9 +885,11 @@ func deinitVideo() {
if !Nan0.hackSkipHwContextDestroy {
C.bridge_context_reset(Nan0.Video.hw.context_destroy)
}
if err := Nan0.sdlCtx.Deinit(); err != nil {
Nan0.log.Error().Err(err).Msg("deinit fail")
}
thread.Main(func() {
if err := Nan0.sdlCtx.Deinit(); err != nil {
Nan0.log.Error().Err(err).Msg("deinit fail")
}
})
Nan0.Video.gl.enabled = false
Nan0.Video.gl.autoCtx = false
Nan0.hackSkipHwContextDestroy = false