From 58a19affcba89f20085de852a3cf8b6aaf5551c0 Mon Sep 17 00:00:00 2001 From: sergystepanov Date: Sat, 27 Dec 2025 01:58:14 +0300 Subject: [PATCH] Clean SDL/OpenGL functions --- pkg/worker/caged/libretro/graphics/opengl.go | 121 +++++-------- pkg/worker/caged/libretro/graphics/sdl.go | 166 +++++++----------- .../caged/libretro/nanoarch/nanoarch.go | 51 +++--- 3 files changed, 135 insertions(+), 203 deletions(-) diff --git a/pkg/worker/caged/libretro/graphics/opengl.go b/pkg/worker/caged/libretro/graphics/opengl.go index 44a9a92f..fca78a6b 100644 --- a/pkg/worker/caged/libretro/graphics/opengl.go +++ b/pkg/worker/caged/libretro/graphics/opengl.go @@ -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 } diff --git a/pkg/worker/caged/libretro/graphics/sdl.go b/pkg/worker/caged/libretro/graphics/sdl.go index 7c25efbd..7c885d88 100644 --- a/pkg/worker/caged/libretro/graphics/sdl.go +++ b/pkg/worker/caged/libretro/graphics/sdl.go @@ -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) } diff --git a/pkg/worker/caged/libretro/nanoarch/nanoarch.go b/pkg/worker/caged/libretro/nanoarch/nanoarch.go index e0be87fd..6153f80e 100644 --- a/pkg/worker/caged/libretro/nanoarch/nanoarch.go +++ b/pkg/worker/caged/libretro/nanoarch/nanoarch.go @@ -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