From dfb78c458db5dc458cc78e028a8461d7e23be548 Mon Sep 17 00:00:00 2001 From: sergystepanov Date: Mon, 29 Jun 2026 23:24:09 +0300 Subject: [PATCH] Replace SDL2 with lightweight RGFW-like GL/Window helper lib --- Dockerfile | 18 +- go.mod | 7 +- go.sum | 14 +- pkg/worker/caged/libretro/frontend.go | 2 +- .../libretro/graphics/gl/KHR/khrplatform.h | 311 ---------- pkg/worker/caged/libretro/graphics/gl/gl.go | 274 --------- pkg/worker/caged/libretro/graphics/opengl.go | 279 ++++++++- pkg/worker/caged/libretro/graphics/rgfw.go | 575 ++++++++++++++++++ pkg/worker/caged/libretro/graphics/sdl.go | 100 --- .../caged/libretro/nanoarch/nanoarch.go | 14 +- 10 files changed, 850 insertions(+), 744 deletions(-) delete mode 100644 pkg/worker/caged/libretro/graphics/gl/KHR/khrplatform.h delete mode 100644 pkg/worker/caged/libretro/graphics/gl/gl.go create mode 100644 pkg/worker/caged/libretro/graphics/rgfw.go delete mode 100644 pkg/worker/caged/libretro/graphics/sdl.go diff --git a/Dockerfile b/Dockerfile index 03e0ba29..0e822a77 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,9 +1,12 @@ ARG BUILD_PATH=/tmp/cloud-game ARG VERSION=master +ARG GO_VERSION=1.26.4 +ARG GSTREAMER_VERSION=1.29.2 + # gstreamer minimal build FROM ubuntu:resolute AS gst-builder -ARG GST_VERSION=1.28.4 +ARG GSTREAMER_VERSION ENV DEBIAN_FRONTEND=noninteractive RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ @@ -16,7 +19,7 @@ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ WORKDIR /gst -RUN git clone --single-branch --depth 1 --branch ${GST_VERSION} \ +RUN git clone --single-branch --depth 1 --branch ${GSTREAMER_VERSION} \ https://gitlab.freedesktop.org/gstreamer/gstreamer.git . # fix meson build error @@ -29,6 +32,8 @@ RUN meson setup builddir \ -Doptimization=3 \ -Db_lto=true \ -Dauto_features=disabled \ + -Dextra-checks=disabled \ + -Dbenchmarks=disabled \ -Dtools=disabled \ -Dgstreamer:tools=disabled \ -Dbase=enabled \ @@ -62,8 +67,8 @@ RUN meson setup builddir \ # base build stage FROM ubuntu:resolute AS build0 ENV DEBIAN_FRONTEND=noninteractive -ARG GO=1.26.4 -ARG GO_DIST=go${GO}.linux-amd64.tar.gz +ARG GO_VERSION +ARG GO_DIST=go${GO_VERSION}.linux-amd64.tar.gz ADD https://go.dev/dl/$GO_DIST ./ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ @@ -107,9 +112,10 @@ COPY --from=gst-builder /usr/local /usr/local RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ --mount=type=cache,target=/var/lib/apt,sharing=locked \ apt-get -q update && apt-get -q install --no-install-recommends -y \ - build-essential \ - libsdl2-dev \ pkg-config \ + build-essential \ + libx11-dev \ + libgl1-mesa-dev \ libglib2.0-dev && \ rm -rf /var/lib/apt/lists/* diff --git a/go.mod b/go.mod index 94bcb74e..f841bf70 100644 --- a/go.mod +++ b/go.mod @@ -12,14 +12,13 @@ require ( github.com/klauspost/cpuid/v2 v2.3.0 github.com/knadh/koanf/maps v0.1.2 github.com/knadh/koanf/v2 v2.3.5 - github.com/minio/minio-go/v7 v7.2.0 + github.com/minio/minio-go/v7 v7.2.1 github.com/pion/ice/v4 v4.2.7 github.com/pion/interceptor v0.1.45 github.com/pion/logging v0.2.4 - github.com/pion/webrtc/v4 v4.2.15 + github.com/pion/webrtc/v4 v4.2.16 github.com/rs/xid v1.6.0 github.com/rs/zerolog v1.35.1 - github.com/veandco/go-sdl2 v0.4.40 golang.org/x/crypto v0.53.0 golang.org/x/image v0.38.0 gopkg.in/yaml.v3 v3.0.1 @@ -47,7 +46,7 @@ require ( github.com/pion/randutil v0.1.0 // indirect github.com/pion/rtcp v1.2.16 // indirect github.com/pion/rtp v1.10.2 // indirect - github.com/pion/sctp v1.10.1 // indirect + github.com/pion/sctp v1.10.3 // indirect github.com/pion/sdp/v3 v3.0.19 // indirect github.com/pion/srtp/v3 v3.0.12 // indirect github.com/pion/stun/v3 v3.1.6 // indirect diff --git a/go.sum b/go.sum index 516ed7ad..127566f5 100644 --- a/go.sum +++ b/go.sum @@ -48,8 +48,8 @@ github.com/minio/crc64nvme v1.1.1 h1:8dwx/Pz49suywbO+auHCBpCtlW1OfpcLN7wYgVR6wAI github.com/minio/crc64nvme v1.1.1/go.mod h1:eVfm2fAzLlxMdUGc0EEBGSMmPwmXD5XiNRpnu9J3bvg= github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34= github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM= -github.com/minio/minio-go/v7 v7.2.0 h1:RCJM0R1XOsRs+A3x3UCaf3ZYbByDaLjFeAi+YCQEPhs= -github.com/minio/minio-go/v7 v7.2.0/go.mod h1:EU9hENAStx/xXduNdrGO5e4X5vk19NtgB+RIPjZO8o0= +github.com/minio/minio-go/v7 v7.2.1 h1:PfBfwvKB/MmqyN8Vb1G9voWisaM9OrLv+WwOvMwS9Dw= +github.com/minio/minio-go/v7 v7.2.1/go.mod h1:EU9hENAStx/xXduNdrGO5e4X5vk19NtgB+RIPjZO8o0= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= @@ -74,8 +74,8 @@ github.com/pion/rtcp v1.2.16 h1:fk1B1dNW4hsI78XUCljZJlC4kZOPk67mNRuQ0fcEkSo= github.com/pion/rtcp v1.2.16/go.mod h1:/as7VKfYbs5NIb4h6muQ35kQF/J0ZVNz2Z3xKoCBYOo= github.com/pion/rtp v1.10.2 h1:l+f6tTDcAH6xwepaAoW791ddhuYsJlqRATOzirO04Mo= github.com/pion/rtp v1.10.2/go.mod h1:Au8fc6cEByy8RLTwKTQTEeQqDB/SJDxwL4mZuxYA5Pk= -github.com/pion/sctp v1.10.1 h1:IDwqRiteu1gqPnqYysDLxVTwIOMFR0zvwOS6cjhzX7k= -github.com/pion/sctp v1.10.1/go.mod h1:N20Dq6LY+JvJDAh9VVh1JELngb2rQ8dPgds5yBWiPgw= +github.com/pion/sctp v1.10.3 h1:1gBtLMA9lmwNuJkZSZJCdD5/Hz4yJs+7dAqi6ZY97QI= +github.com/pion/sctp v1.10.3/go.mod h1:7KFmTwLcoYgJs/Z+99nJvsWL0qDpuyloSI0RbAqlrz0= github.com/pion/sdp/v3 v3.0.19 h1:1VMKs3gIkTQV5M3hNKfTAPrDXSNrYtOlmOD8+mSZUGQ= github.com/pion/sdp/v3 v3.0.19/go.mod h1:dE5WOSlzXrtiE/iuZqe9n+AcEbOjtAd3k5m5NtlV/qU= github.com/pion/srtp/v3 v3.0.12 h1:U7V17bckl7sI4mb3sepiojByDuBY0wNCqQE+6IlQBbc= @@ -88,8 +88,8 @@ github.com/pion/transport/v4 v4.0.2 h1:ifYlPqNwsy6aKQ9y8yzxXlHae5431ZrH2avkD/Rn6 github.com/pion/transport/v4 v4.0.2/go.mod h1:06hFI+jCFcok2X2MekVufNZ/uzNZXivGBPfviSVcjgM= github.com/pion/turn/v5 v5.0.10 h1:mOMZjudflXpte5OsCnXztpUKwNXcpXIAzMBnq9TXOSQ= github.com/pion/turn/v5 v5.0.10/go.mod h1:u3XjBqy2Z4+NhCUpDoOSsNuQDrPLvKStlCGWk6sTQ1E= -github.com/pion/webrtc/v4 v4.2.15 h1:Ir/MauNFCfg+kgyBYPQLiGdVWFlzEcLxqtuzAkYkky0= -github.com/pion/webrtc/v4 v4.2.15/go.mod h1:CPTcyLfIzC4scOkQ4UY4pj6WvbUGhcNLIpK28cP5h6M= +github.com/pion/webrtc/v4 v4.2.16 h1:oK1GAg0TWJtZWYB8J/BgTgGWPoV2148gQWocH12vr3Q= +github.com/pion/webrtc/v4 v4.2.16/go.mod h1:y4HjLAkX90LH+C/qPqGOUgz8RA8CbDj3Iar3d+2hdKQ= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= @@ -113,8 +113,6 @@ github.com/valyala/fastrand v1.1.0 h1:f+5HkLW4rsgzdNoleUOB69hyT9IlD2ZQh9GyDMfb5G github.com/valyala/fastrand v1.1.0/go.mod h1:HWqCzkrkg6QXT8V2EXWvXCoow7vLwOFN002oeRzjapQ= github.com/valyala/histogram v1.2.0 h1:wyYGAZZt3CpwUiIb9AU/Zbllg1llXyrtApRS815OLoQ= github.com/valyala/histogram v1.2.0/go.mod h1:Hb4kBwb4UxsaNbbbh+RRz8ZR6pdodR57tzWUS3BUzXY= -github.com/veandco/go-sdl2 v0.4.40 h1:fZv6wC3zz1Xt167P09gazawnpa0KY5LM7JAvKpX9d/U= -github.com/veandco/go-sdl2 v0.4.40/go.mod h1:OROqMhHD43nT4/i9crJukyVecjPNYYuCofep6SNiAjY= github.com/wlynxg/anet v0.0.5 h1:J3VJGi1gvo0JwZ/P1/Yc/8p63SoW98B5dHkYDmpgvvU= github.com/wlynxg/anet v0.0.5/go.mod h1:eay5PRQr7fIVAMbTbchTnO9gG65Hg/uYGdc7mguHxoA= github.com/zeebo/assert v1.3.0 h1:g7C04CbJuIDKNPFHmsk4hwZDO5O+kntRxzaUoNXj+IQ= diff --git a/pkg/worker/caged/libretro/frontend.go b/pkg/worker/caged/libretro/frontend.go index b1b16378..b9d86abe 100644 --- a/pkg/worker/caged/libretro/frontend.go +++ b/pkg/worker/caged/libretro/frontend.go @@ -522,7 +522,7 @@ func (f *Frontend) Load() error { } func (f *Frontend) IsSupported() error { - return graphics.TryInit() + return graphics.RGFWTryInit() } func (f *Frontend) autosave(periodSec int) { diff --git a/pkg/worker/caged/libretro/graphics/gl/KHR/khrplatform.h b/pkg/worker/caged/libretro/graphics/gl/KHR/khrplatform.h deleted file mode 100644 index 01646449..00000000 --- a/pkg/worker/caged/libretro/graphics/gl/KHR/khrplatform.h +++ /dev/null @@ -1,311 +0,0 @@ -#ifndef __khrplatform_h_ -#define __khrplatform_h_ - -/* -** Copyright (c) 2008-2018 The Khronos Group Inc. -** -** Permission is hereby granted, free of charge, to any person obtaining a -** copy of this software and/or associated documentation files (the -** "Materials"), to deal in the Materials without restriction, including -** without limitation the rights to use, copy, modify, merge, publish, -** distribute, sublicense, and/or sell copies of the Materials, and to -** permit persons to whom the Materials are furnished to do so, subject to -** the following conditions: -** -** The above copyright notice and this permission notice shall be included -** in all copies or substantial portions of the Materials. -** -** THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY -** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, -** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -** MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS. -*/ - -/* Khronos platform-specific types and definitions. - * - * The master copy of khrplatform.h is maintained in the Khronos EGL - * Registry repository at https://github.com/KhronosGroup/EGL-Registry - * The last semantic modification to khrplatform.h was at commit ID: - * 67a3e0864c2d75ea5287b9f3d2eb74a745936692 - * - * Adopters may modify this file to suit their platform. Adopters are - * encouraged to submit platform specific modifications to the Khronos - * group so that they can be included in future versions of this file. - * Please submit changes by filing pull requests or issues on - * the EGL Registry repository linked above. - * - * - * See the Implementer's Guidelines for information about where this file - * should be located on your system and for more details of its use: - * http://www.khronos.org/registry/implementers_guide.pdf - * - * This file should be included as - * #include - * by Khronos client API header files that use its types and defines. - * - * The types in khrplatform.h should only be used to define API-specific types. - * - * Types defined in khrplatform.h: - * khronos_int8_t signed 8 bit - * khronos_uint8_t unsigned 8 bit - * khronos_int16_t signed 16 bit - * khronos_uint16_t unsigned 16 bit - * khronos_int32_t signed 32 bit - * khronos_uint32_t unsigned 32 bit - * khronos_int64_t signed 64 bit - * khronos_uint64_t unsigned 64 bit - * khronos_intptr_t signed same number of bits as a pointer - * khronos_uintptr_t unsigned same number of bits as a pointer - * khronos_ssize_t signed size - * khronos_usize_t unsigned size - * khronos_float_t signed 32 bit floating point - * khronos_time_ns_t unsigned 64 bit time in nanoseconds - * khronos_utime_nanoseconds_t unsigned time interval or absolute time in - * nanoseconds - * khronos_stime_nanoseconds_t signed time interval in nanoseconds - * khronos_boolean_enum_t enumerated boolean type. This should - * only be used as a base type when a client API's boolean type is - * an enum. Client APIs which use an integer or other type for - * booleans cannot use this as the base type for their boolean. - * - * Tokens defined in khrplatform.h: - * - * KHRONOS_FALSE, KHRONOS_TRUE Enumerated boolean false/true values. - * - * KHRONOS_SUPPORT_INT64 is 1 if 64 bit integers are supported; otherwise 0. - * KHRONOS_SUPPORT_FLOAT is 1 if floats are supported; otherwise 0. - * - * Calling convention macros defined in this file: - * KHRONOS_APICALL - * KHRONOS_APIENTRY - * KHRONOS_APIATTRIBUTES - * - * These may be used in function prototypes as: - * - * KHRONOS_APICALL void KHRONOS_APIENTRY funcname( - * int arg1, - * int arg2) KHRONOS_APIATTRIBUTES; - */ - -#if defined(__SCITECH_SNAP__) && !defined(KHRONOS_STATIC) -# define KHRONOS_STATIC 1 -#endif - -/*------------------------------------------------------------------------- - * Definition of KHRONOS_APICALL - *------------------------------------------------------------------------- - * This precedes the return type of the function in the function prototype. - */ -#if defined(KHRONOS_STATIC) - /* If the preprocessor constant KHRONOS_STATIC is defined, make the - * header compatible with static linking. */ -# define KHRONOS_APICALL -#elif defined(_WIN32) -# define KHRONOS_APICALL __declspec(dllimport) -#elif defined (__SYMBIAN32__) -# define KHRONOS_APICALL IMPORT_C -#elif defined(__ANDROID__) -# define KHRONOS_APICALL __attribute__((visibility("default"))) -#else -# define KHRONOS_APICALL -#endif - -/*------------------------------------------------------------------------- - * Definition of KHRONOS_APIENTRY - *------------------------------------------------------------------------- - * This follows the return type of the function and precedes the function - * name in the function prototype. - */ -#if defined(_WIN32) && !defined(_WIN32_WCE) && !defined(__SCITECH_SNAP__) - /* Win32 but not WinCE */ -# define KHRONOS_APIENTRY __stdcall -#else -# define KHRONOS_APIENTRY -#endif - -/*------------------------------------------------------------------------- - * Definition of KHRONOS_APIATTRIBUTES - *------------------------------------------------------------------------- - * This follows the closing parenthesis of the function prototype arguments. - */ -#if defined (__ARMCC_2__) -#define KHRONOS_APIATTRIBUTES __softfp -#else -#define KHRONOS_APIATTRIBUTES -#endif - -/*------------------------------------------------------------------------- - * basic type definitions - *-----------------------------------------------------------------------*/ -#if (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || defined(__GNUC__) || defined(__SCO__) || defined(__USLC__) - - -/* - * Using - */ -#include -typedef int32_t khronos_int32_t; -typedef uint32_t khronos_uint32_t; -typedef int64_t khronos_int64_t; -typedef uint64_t khronos_uint64_t; -#define KHRONOS_SUPPORT_INT64 1 -#define KHRONOS_SUPPORT_FLOAT 1 -/* - * To support platform where unsigned long cannot be used interchangeably with - * inptr_t (e.g. CHERI-extended ISAs), we can use the stdint.h intptr_t. - * Ideally, we could just use (u)intptr_t everywhere, but this could result in - * ABI breakage if khronos_uintptr_t is changed from unsigned long to - * unsigned long long or similar (this results in different C++ name mangling). - * To avoid changes for existing platforms, we restrict usage of intptr_t to - * platforms where the size of a pointer is larger than the size of long. - */ -#if defined(__SIZEOF_LONG__) && defined(__SIZEOF_POINTER__) -#if __SIZEOF_POINTER__ > __SIZEOF_LONG__ -#define KHRONOS_USE_INTPTR_T -#endif -#endif - -#elif defined(__VMS ) || defined(__sgi) - -/* - * Using - */ -#include -typedef int32_t khronos_int32_t; -typedef uint32_t khronos_uint32_t; -typedef int64_t khronos_int64_t; -typedef uint64_t khronos_uint64_t; -#define KHRONOS_SUPPORT_INT64 1 -#define KHRONOS_SUPPORT_FLOAT 1 - -#elif defined(_WIN32) && !defined(__SCITECH_SNAP__) - -/* - * Win32 - */ -typedef __int32 khronos_int32_t; -typedef unsigned __int32 khronos_uint32_t; -typedef __int64 khronos_int64_t; -typedef unsigned __int64 khronos_uint64_t; -#define KHRONOS_SUPPORT_INT64 1 -#define KHRONOS_SUPPORT_FLOAT 1 - -#elif defined(__sun__) || defined(__digital__) - -/* - * Sun or Digital - */ -typedef int khronos_int32_t; -typedef unsigned int khronos_uint32_t; -#if defined(__arch64__) || defined(_LP64) -typedef long int khronos_int64_t; -typedef unsigned long int khronos_uint64_t; -#else -typedef long long int khronos_int64_t; -typedef unsigned long long int khronos_uint64_t; -#endif /* __arch64__ */ -#define KHRONOS_SUPPORT_INT64 1 -#define KHRONOS_SUPPORT_FLOAT 1 - -#elif 0 - -/* - * Hypothetical platform with no float or int64 support - */ -typedef int khronos_int32_t; -typedef unsigned int khronos_uint32_t; -#define KHRONOS_SUPPORT_INT64 0 -#define KHRONOS_SUPPORT_FLOAT 0 - -#else - -/* - * Generic fallback - */ -#include -typedef int32_t khronos_int32_t; -typedef uint32_t khronos_uint32_t; -typedef int64_t khronos_int64_t; -typedef uint64_t khronos_uint64_t; -#define KHRONOS_SUPPORT_INT64 1 -#define KHRONOS_SUPPORT_FLOAT 1 - -#endif - - -/* - * Types that are (so far) the same on all platforms - */ -typedef signed char khronos_int8_t; -typedef unsigned char khronos_uint8_t; -typedef signed short int khronos_int16_t; -typedef unsigned short int khronos_uint16_t; - -/* - * Types that differ between LLP64 and LP64 architectures - in LLP64, - * pointers are 64 bits, but 'long' is still 32 bits. Win64 appears - * to be the only LLP64 architecture in current use. - */ -#ifdef KHRONOS_USE_INTPTR_T -typedef intptr_t khronos_intptr_t; -typedef uintptr_t khronos_uintptr_t; -#elif defined(_WIN64) -typedef signed long long int khronos_intptr_t; -typedef unsigned long long int khronos_uintptr_t; -#else -typedef signed long int khronos_intptr_t; -typedef unsigned long int khronos_uintptr_t; -#endif - -#if defined(_WIN64) -typedef signed long long int khronos_ssize_t; -typedef unsigned long long int khronos_usize_t; -#else -typedef signed long int khronos_ssize_t; -typedef unsigned long int khronos_usize_t; -#endif - -#if KHRONOS_SUPPORT_FLOAT -/* - * Float type - */ -typedef float khronos_float_t; -#endif - -#if KHRONOS_SUPPORT_INT64 -/* Time types - * - * These types can be used to represent a time interval in nanoseconds or - * an absolute Unadjusted System Time. Unadjusted System Time is the number - * of nanoseconds since some arbitrary system event (e.g. since the last - * time the system booted). The Unadjusted System Time is an unsigned - * 64 bit value that wraps back to 0 every 584 years. Time intervals - * may be either signed or unsigned. - */ -typedef khronos_uint64_t khronos_utime_nanoseconds_t; -typedef khronos_int64_t khronos_stime_nanoseconds_t; -#endif - -/* - * Dummy value used to pad enum types to 32 bits. - */ -#ifndef KHRONOS_MAX_ENUM -#define KHRONOS_MAX_ENUM 0x7FFFFFFF -#endif - -/* - * Enumerated boolean type - * - * Values other than zero should be considered to be true. Therefore - * comparisons should not be made against KHRONOS_TRUE. - */ -typedef enum { - KHRONOS_FALSE = 0, - KHRONOS_TRUE = 1, - KHRONOS_BOOLEAN_ENUM_FORCE_SIZE = KHRONOS_MAX_ENUM -} khronos_boolean_enum_t; - -#endif /* __khrplatform_h_ */ diff --git a/pkg/worker/caged/libretro/graphics/gl/gl.go b/pkg/worker/caged/libretro/graphics/gl/gl.go deleted file mode 100644 index 46e3842e..00000000 --- a/pkg/worker/caged/libretro/graphics/gl/gl.go +++ /dev/null @@ -1,274 +0,0 @@ -package gl - -// Custom OpenGL bindings -// Based on https://github.com/go-gl/gl/tree/master/v2.1/gl - -/* -#cgo egl,windows LDFLAGS: -lEGL -#cgo egl,darwin LDFLAGS: -lEGL -#cgo !gles2,darwin LDFLAGS: -framework OpenGL -#cgo gles2,darwin LDFLAGS: -lGLESv2 -#cgo !gles2,windows LDFLAGS: -lopengl32 -#cgo gles2,windows LDFLAGS: -lGLESv2 -#cgo !egl,linux !egl,freebsd !egl,openbsd pkg-config: gl -#cgo egl,linux egl,freebsd egl,openbsd pkg-config: egl - -#if defined(_WIN32) && !defined(APIENTRY) && !defined(__CYGWIN__) && !defined(__SCITECH_SNAP__) -#ifndef WIN32_LEAN_AND_MEAN -#define WIN32_LEAN_AND_MEAN 1 -#endif - -#include - -#endif -#ifndef APIENTRY -#define APIENTRY -#endif -#ifndef APIENTRYP -#define APIENTRYP APIENTRY* -#endif -#ifndef GLAPI -#define GLAPI extern -#endif - -#include - -typedef unsigned int GLenum; -typedef unsigned char GLboolean; -typedef unsigned int GLbitfield; -typedef khronos_int8_t GLbyte; -typedef khronos_uint8_t GLubyte; -typedef khronos_int16_t GLshort; -typedef khronos_uint16_t GLushort; -typedef int GLint; -typedef unsigned int GLuint; -typedef khronos_int32_t GLclampx; -typedef int GLsizei; -typedef khronos_float_t GLfloat; -typedef khronos_float_t GLclampf; -typedef double GLdouble; -typedef double GLclampd; -typedef void *GLeglClientBufferEXT; -typedef void *GLeglImageOES; -typedef char GLchar; -typedef char GLcharARB; - - -#ifdef __APPLE__ -typedef void *GLhandleARB; -#else -typedef unsigned int GLhandleARB; -#endif - -typedef const GLubyte *(APIENTRYP GPGETSTRING)(GLenum name); -typedef GLenum (APIENTRYP GPCHECKFRAMEBUFFERSTATUS)(GLenum target); -typedef GLenum (APIENTRYP GPGETERROR)(); -typedef void (APIENTRYP GPBINDFRAMEBUFFER)(GLenum target, GLuint framebuffer); -typedef void (APIENTRYP GPBINDRENDERBUFFER)(GLenum target, GLuint renderbuffer); -typedef void (APIENTRYP GPBINDTEXTURE)(GLenum target, GLuint texture); -typedef void (APIENTRYP GPDELETEFRAMEBUFFERS)(GLsizei n, const GLuint *framebuffers); -typedef void (APIENTRYP GPDELETERENDERBUFFERS)(GLsizei n, const GLuint *renderbuffers); -typedef void (APIENTRYP GPDELETETEXTURES)(GLsizei n, const GLuint* textures); -typedef void (APIENTRYP GPFRAMEBUFFERRENDERBUFFER)(GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer); -typedef void (APIENTRYP GPFRAMEBUFFERTEXTURE2D)(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); -typedef void (APIENTRYP GPGENFRAMEBUFFERS)(GLsizei n, GLuint *framebuffers); -typedef void (APIENTRYP GPGENRENDERBUFFERS)(GLsizei n, GLuint *renderbuffers); -typedef void (APIENTRYP GPGENTEXTURES)(GLsizei n, GLuint *textures); -typedef void (APIENTRYP GPREADPIXELS)(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, void *pixels); -typedef void (APIENTRYP GPRENDERBUFFERSTORAGE)(GLenum target, GLenum internalformat, GLsizei width, GLsizei height); -typedef void (APIENTRYP GPTEXIMAGE2D)(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const void *pixels); -typedef void (APIENTRYP GPTEXPARAMETERI)(GLenum target, GLenum pname, GLint param); -typedef void (APIENTRYP GPPIXELSTOREI)(GLenum pname, GLint param); - -static const GLubyte *getString(GPGETSTRING ptr, GLenum name) { return (*ptr)(name); } -static GLenum getError(GPGETERROR ptr) { return (*ptr)(); } -static void bindTexture(GPBINDTEXTURE ptr, GLenum target, GLuint texture) { (*ptr)(target, texture); } -static void bindFramebuffer(GPBINDFRAMEBUFFER ptr, GLenum target, GLuint framebuffer) { (*ptr)(target, framebuffer); } -static void bindRenderbuffer(GPBINDRENDERBUFFER ptr, GLenum target, GLuint buf) { (*ptr)(target, buf); } -static void texParameteri(GPTEXPARAMETERI ptr, GLenum target, GLenum pname, GLint param) { - (*ptr)(target, pname, param); -} -static void texImage2D(GPTEXIMAGE2D ptr, GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const void *pixels) { - (*ptr)(target, level, internalformat, width, height, border, format, type, pixels); -} -static void genFramebuffers(GPGENFRAMEBUFFERS ptr, GLsizei n, GLuint *framebuffers) { (*ptr)(n, framebuffers); } -static void genTextures(GPGENTEXTURES ptr, GLsizei n, GLuint *textures) { (*ptr)(n, textures); } -static void framebufferTexture2D(GPFRAMEBUFFERTEXTURE2D ptr, GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level) { - (*ptr)(target, attachment, textarget, texture, level); -} -static void genRenderbuffers(GPGENRENDERBUFFERS ptr, GLsizei n, GLuint *renderbuffers) { (*ptr)(n, renderbuffers); } -static void renderbufferStorage(GPRENDERBUFFERSTORAGE ptr, GLenum target, GLenum internalformat, GLsizei width, GLsizei height) { - (*ptr)(target, internalformat, width, height); -} -static void framebufferRenderbuffer(GPFRAMEBUFFERRENDERBUFFER ptr, GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer) { - (*ptr)(target, attachment, renderbuffertarget, renderbuffer); -} -static GLenum checkFramebufferStatus(GPCHECKFRAMEBUFFERSTATUS ptr, GLenum target) { return (*ptr)(target); } -static void deleteRenderbuffers(GPDELETERENDERBUFFERS ptr, GLsizei n, const GLuint *renderbuffers) { - (*ptr)(n, renderbuffers); -} -static void deleteFramebuffers(GPDELETEFRAMEBUFFERS ptr, GLsizei n, const GLuint *framebuffers) { - (*ptr)(n, framebuffers); -} -static void deleteTextures(GPDELETETEXTURES ptr, GLsizei n, const GLuint *textures) { (*ptr)(n, textures); } -static void readPixels(GPREADPIXELS ptr, GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, void *pixels) { - (*ptr)(x, y, width, height, format, type, pixels); -} -static void pixelStorei(GPPIXELSTOREI ptr, GLenum pname, GLint param) { (*ptr)(pname, param); } -*/ -import "C" -import ( - "errors" - "unsafe" -) - -const ( - VENDOR = 0x1F00 - VERSION = 0x1F02 - RENDERER = 0x1F01 - ShadingLanguageVersion = 0x8B8C - Texture2d = 0x0DE1 - RENDERBUFFER = 0x8D41 - FRAMEBUFFER = 0x8D40 - TextureMinFilter = 0x2801 - TextureMagFilter = 0x2800 - NEAREST = 0x2600 - RGBA8 = 0x8058 - BGRA = 0x80E1 - RGB = 0x1907 - ColorAttachment0 = 0x8CE0 - Depth24Stencil8 = 0x88F0 - DepthStencilAttachment = 0x821A - DepthComponent24 = 0x81A6 - DepthAttachment = 0x8D00 - FramebufferComplete = 0x8CD5 - - UnsignedShort5551 = 0x8034 - UnsignedShort565 = 0x8363 - UnsignedInt8888Rev = 0x8367 - - PackAlignment = 0x0D05 -) - -var ( - gpGetString C.GPGETSTRING - gpGenTextures C.GPGENTEXTURES - gpGetError C.GPGETERROR - gpBindTexture C.GPBINDTEXTURE - gpBindFramebuffer C.GPBINDFRAMEBUFFER - gpTexParameteri C.GPTEXPARAMETERI - gpTexImage2D C.GPTEXIMAGE2D - gpGenFramebuffers C.GPGENFRAMEBUFFERS - gpFramebufferTexture2D C.GPFRAMEBUFFERTEXTURE2D - gpGenRenderbuffers C.GPGENRENDERBUFFERS - gpBindRenderbuffer C.GPBINDRENDERBUFFER - gpRenderbufferStorage C.GPRENDERBUFFERSTORAGE - gpFramebufferRenderbuffer C.GPFRAMEBUFFERRENDERBUFFER - gpCheckFramebufferStatus C.GPCHECKFRAMEBUFFERSTATUS - gpDeleteRenderbuffers C.GPDELETERENDERBUFFERS - gpDeleteFramebuffers C.GPDELETEFRAMEBUFFERS - gpDeleteTextures C.GPDELETETEXTURES - gpReadPixels C.GPREADPIXELS - gpPixelStorei C.GPPIXELSTOREI -) - -func InitWithProcAddrFunc(getProcAddr func(name string) unsafe.Pointer) error { - if gpGetString = (C.GPGETSTRING)(getProcAddr("glGetString")); gpGetString == nil { - return errors.New("glGetString") - } - if gpGenTextures = (C.GPGENTEXTURES)(getProcAddr("glGenTextures")); gpGenTextures == nil { - return errors.New("glGenTextures") - } - if gpGetError = (C.GPGETERROR)(getProcAddr("glGetError")); gpGetError == nil { - return errors.New("glGetError") - } - if gpBindTexture = (C.GPBINDTEXTURE)(getProcAddr("glBindTexture")); gpBindTexture == nil { - return errors.New("glBindTexture") - } - if gpBindFramebuffer = (C.GPBINDFRAMEBUFFER)(getProcAddr("glBindFramebuffer")); gpBindFramebuffer == nil { - return errors.New("glBindFramebuffer") - } - if gpTexParameteri = (C.GPTEXPARAMETERI)(getProcAddr("glTexParameteri")); gpTexParameteri == nil { - return errors.New("glTexParameteri") - } - if gpTexImage2D = (C.GPTEXIMAGE2D)(getProcAddr("glTexImage2D")); gpTexImage2D == nil { - return errors.New("glTexImage2D") - } - gpGenFramebuffers = (C.GPGENFRAMEBUFFERS)(getProcAddr("glGenFramebuffers")) - gpFramebufferTexture2D = (C.GPFRAMEBUFFERTEXTURE2D)(getProcAddr("glFramebufferTexture2D")) - gpGenRenderbuffers = (C.GPGENRENDERBUFFERS)(getProcAddr("glGenRenderbuffers")) - gpBindRenderbuffer = (C.GPBINDRENDERBUFFER)(getProcAddr("glBindRenderbuffer")) - gpRenderbufferStorage = (C.GPRENDERBUFFERSTORAGE)(getProcAddr("glRenderbufferStorage")) - gpFramebufferRenderbuffer = (C.GPFRAMEBUFFERRENDERBUFFER)(getProcAddr("glFramebufferRenderbuffer")) - gpCheckFramebufferStatus = (C.GPCHECKFRAMEBUFFERSTATUS)(getProcAddr("glCheckFramebufferStatus")) - gpDeleteRenderbuffers = (C.GPDELETERENDERBUFFERS)(getProcAddr("glDeleteRenderbuffers")) - gpDeleteFramebuffers = (C.GPDELETEFRAMEBUFFERS)(getProcAddr("glDeleteFramebuffers")) - if gpDeleteTextures = (C.GPDELETETEXTURES)(getProcAddr("glDeleteTextures")); gpDeleteTextures == nil { - return errors.New("glDeleteTextures") - } - gpReadPixels = (C.GPREADPIXELS)(getProcAddr("glReadPixels")) - if gpReadPixels == nil { - return errors.New("glReadPixels") - } - if gpPixelStorei = (C.GPPIXELSTOREI)(getProcAddr("glPixelStorei")); gpPixelStorei == nil { - return errors.New("glPixelStorei") - } - return nil -} - -func GetString(name uint32) *uint8 { return (*uint8)(C.getString(gpGetString, (C.GLenum)(name))) } -func GenTextures(n int32, textures *uint32) { - C.genTextures(gpGenTextures, (C.GLsizei)(n), (*C.GLuint)(unsafe.Pointer(textures))) -} -func BindTexture(target uint32, texture uint32) { - C.bindTexture(gpBindTexture, (C.GLenum)(target), (C.GLuint)(texture)) -} -func BindFramebuffer(target uint32, framebuffer uint32) { - C.bindFramebuffer(gpBindFramebuffer, (C.GLenum)(target), (C.GLuint)(framebuffer)) -} -func TexParameteri(target uint32, name uint32, param int32) { - C.texParameteri(gpTexParameteri, (C.GLenum)(target), (C.GLenum)(name), (C.GLint)(param)) -} -func TexImage2D(target uint32, level int32, internalformat int32, width int32, height int32, border int32, format uint32, xtype uint32, pixels unsafe.Pointer) { - C.texImage2D(gpTexImage2D, (C.GLenum)(target), (C.GLint)(level), (C.GLint)(internalformat), (C.GLsizei)(width), (C.GLsizei)(height), (C.GLint)(border), (C.GLenum)(format), (C.GLenum)(xtype), pixels) -} -func GenFramebuffers(n int32, framebuffers *uint32) { - C.genFramebuffers(gpGenFramebuffers, (C.GLsizei)(n), (*C.GLuint)(unsafe.Pointer(framebuffers))) -} -func FramebufferTexture2D(target uint32, attachment uint32, texTarget uint32, texture uint32, level int32) { - C.framebufferTexture2D(gpFramebufferTexture2D, (C.GLenum)(target), (C.GLenum)(attachment), (C.GLenum)(texTarget), (C.GLuint)(texture), (C.GLint)(level)) -} -func GenRenderbuffers(n int32, renderbuffers *uint32) { - C.genRenderbuffers(gpGenRenderbuffers, (C.GLsizei)(n), (*C.GLuint)(unsafe.Pointer(renderbuffers))) -} -func BindRenderbuffer(target uint32, renderbuffer uint32) { - C.bindRenderbuffer(gpBindRenderbuffer, (C.GLenum)(target), (C.GLuint)(renderbuffer)) -} -func RenderbufferStorage(target uint32, internalformat uint32, width int32, height int32) { - C.renderbufferStorage(gpRenderbufferStorage, (C.GLenum)(target), (C.GLenum)(internalformat), (C.GLsizei)(width), (C.GLsizei)(height)) -} -func FramebufferRenderbuffer(target uint32, attachment uint32, renderbufferTarget uint32, renderbuffer uint32) { - C.framebufferRenderbuffer(gpFramebufferRenderbuffer, (C.GLenum)(target), (C.GLenum)(attachment), (C.GLenum)(renderbufferTarget), (C.GLuint)(renderbuffer)) -} -func CheckFramebufferStatus(target uint32) uint32 { - return (uint32)(C.checkFramebufferStatus(gpCheckFramebufferStatus, (C.GLenum)(target))) -} -func DeleteRenderbuffers(n int32, renderbuffers *uint32) { - C.deleteRenderbuffers(gpDeleteRenderbuffers, (C.GLsizei)(n), (*C.GLuint)(unsafe.Pointer(renderbuffers))) -} -func DeleteFramebuffers(n int32, framebuffers *uint32) { - C.deleteFramebuffers(gpDeleteFramebuffers, (C.GLsizei)(n), (*C.GLuint)(unsafe.Pointer(framebuffers))) -} -func DeleteTextures(n int32, textures *uint32) { - C.deleteTextures(gpDeleteTextures, (C.GLsizei)(n), (*C.GLuint)(unsafe.Pointer(textures))) -} -func ReadPixels(x int32, y int32, width int32, height int32, format uint32, xtype uint32, pixels unsafe.Pointer) { - C.readPixels(gpReadPixels, (C.GLint)(x), (C.GLint)(y), (C.GLsizei)(width), (C.GLsizei)(height), (C.GLenum)(format), (C.GLenum)(xtype), pixels) -} -func PixelStorei(pname uint32, param int32) { - C.pixelStorei(gpPixelStorei, (C.GLenum)(pname), (C.GLint)(param)) -} - -func GetError() uint32 { return (uint32)(C.getError(gpGetError)) } - -func GoStr(str *uint8) string { return C.GoString((*C.char)(unsafe.Pointer(str))) } diff --git a/pkg/worker/caged/libretro/graphics/opengl.go b/pkg/worker/caged/libretro/graphics/opengl.go index fca78a6b..0c9d7736 100644 --- a/pkg/worker/caged/libretro/graphics/opengl.go +++ b/pkg/worker/caged/libretro/graphics/opengl.go @@ -1,14 +1,147 @@ package graphics +/* +#cgo windows LDFLAGS: -lopengl32 +#cgo linux LDFLAGS: -lGL +#cgo darwin LDFLAGS: -framework OpenGL + +#include + +#ifndef APIENTRY +#define APIENTRY +#endif +#ifndef APIENTRYP +#define APIENTRYP APIENTRY* +#endif + +typedef unsigned int GLenum; +typedef int GLint; +typedef unsigned int GLuint; +typedef int GLsizei; +typedef unsigned char GLubyte; + +typedef const GLubyte *(APIENTRYP GPGETSTRING)(GLenum name); +typedef GLenum (APIENTRYP GPCHECKFRAMEBUFFERSTATUS)(GLenum target); +typedef GLenum (APIENTRYP GPGETERROR)(); +typedef void (APIENTRYP GPBINDFRAMEBUFFER)(GLenum target, GLuint framebuffer); +typedef void (APIENTRYP GPBINDRENDERBUFFER)(GLenum target, GLuint renderbuffer); +typedef void (APIENTRYP GPBINDTEXTURE)(GLenum target, GLuint texture); +typedef void (APIENTRYP GPDELETEFRAMEBUFFERS)(GLsizei n, const GLuint *framebuffers); +typedef void (APIENTRYP GPDELETERENDERBUFFERS)(GLsizei n, const GLuint *renderbuffers); +typedef void (APIENTRYP GPDELETETEXTURES)(GLsizei n, const GLuint* textures); +typedef void (APIENTRYP GPFRAMEBUFFERRENDERBUFFER)(GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer); +typedef void (APIENTRYP GPFRAMEBUFFERTEXTURE2D)(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level); +typedef void (APIENTRYP GPGENFRAMEBUFFERS)(GLsizei n, GLuint *fb); +typedef void (APIENTRYP GPGENRENDERBUFFERS)(GLsizei n, GLuint *rb); +typedef void (APIENTRYP GPGENTEXTURES)(GLsizei n, GLuint *tex); +typedef void (APIENTRYP GPREADPIXELS)(GLint x, GLint y, GLsizei w, GLsizei h, GLenum f, GLenum t, void *p); +typedef void (APIENTRYP GPRENDERBUFFERSTORAGE)(GLenum t, GLenum i, GLsizei w, GLsizei h); +typedef void (APIENTRYP GPTEXIMAGE2D)(GLenum t, GLint l, GLint i, GLsizei w, GLsizei h, GLint b, GLenum f, GLenum x, const void *px); +typedef void (APIENTRYP GPTEXPARAMETERI)(GLenum t, GLenum n, GLint p); +typedef void (APIENTRYP GPPIXELSTOREI)(GLenum n, GLint p); + +// Generic C wrappers — one per signature pattern, called from Go via function pointers. +static const GLubyte *gl_get_string(GPGETSTRING f, GLenum a) { return f(a); } +static GLenum gl_get_error(GPGETERROR f) { return f(); } +static GLenum gl_check_fb_status(GPCHECKFRAMEBUFFERSTATUS f, GLenum a) { return f(a); } +static void gl_1u32(GPGENTEXTURES f, GLsizei a, GLuint *b) { f(a, b); } +static void gl_bind_tex(GPBINDTEXTURE f, GLenum a, GLuint b) { f(a, b); } +static void gl_bind_fb(GPBINDFRAMEBUFFER f, GLenum a, GLuint b) { f(a, b); } +static void gl_bind_rb(GPBINDRENDERBUFFER f, GLenum a, GLuint b) { f(a, b); } +static void gl_tex_param(GPTEXPARAMETERI f, GLenum a, GLenum b, GLint c) { f(a, b, c); } +static void gl_tex_image(GPTEXIMAGE2D f, GLenum a, GLint b, GLint c, GLsizei d, GLsizei e, GLint g, GLenum h, GLenum i, const void *j) { f(a, b, c, d, e, g, h, i, j); } +static void gl_fb_tex2d(GPFRAMEBUFFERTEXTURE2D f, GLenum a, GLenum b, GLenum c, GLuint d, GLint e) { f(a, b, c, d, e); } +static void gl_fb_rb(GPFRAMEBUFFERRENDERBUFFER f, GLenum a, GLenum b, GLenum c, GLuint d) { f(a, b, c, d); } +static void gl_rb_storage(GPRENDERBUFFERSTORAGE f, GLenum a, GLenum b, GLsizei c, GLsizei d) { f(a, b, c, d); } +static void gl_delete_fb(GPDELETEFRAMEBUFFERS f, GLsizei a, const GLuint *b) { f(a, b); } +static void gl_delete_rb(GPDELETERENDERBUFFERS f, GLsizei a, const GLuint *b) { f(a, b); } +static void gl_delete_tex(GPDELETETEXTURES f, GLsizei a, const GLuint *b) { f(a, b); } +static void gl_read_pixels(GPREADPIXELS f, GLint a, GLint b, GLsizei c, GLsizei d, GLenum e, GLenum g, void *h) { f(a, b, c, d, e, g, h); } +static void gl_pixel_storei(GPPIXELSTOREI f, GLenum a, GLint b) { f(a, b); } +*/ +import "C" import ( "errors" "fmt" "math" "unsafe" - - "github.com/giongto35/cloud-game/v3/pkg/worker/caged/libretro/graphics/gl" ) +const ( + gl_VENDOR = 0x1F00 + gl_VERSION = 0x1F02 + gl_RENDERER = 0x1F01 + gl_SHADING_LANG_VERSION = 0x8B8C + gl_TEXTURE_2D = 0x0DE1 + gl_RENDERBUFFER = 0x8D41 + gl_FRAMEBUFFER = 0x8D40 + gl_TEXTURE_MIN_FILTER = 0x2801 + gl_TEXTURE_MAG_FILTER = 0x2800 + gl_NEAREST = 0x2600 + gl_RGBA = 0x1908 + gl_RGB = 0x1907 + gl_BGRA = 0x80E1 + gl_RGBA8 = 0x8058 + gl_COLOR_ATTACHMENT0 = 0x8CE0 + gl_DEPTH_ATTACHMENT = 0x8D00 + gl_DEPTH_STENCIL_ATTACH = 0x821A + gl_DEPTH_COMPONENT24 = 0x81A6 + gl_DEPTH24_STENCIL8 = 0x88F0 + gl_FRAMEBUFFER_COMPLETE = 0x8CD5 + gl_PACK_ALIGNMENT = 0x0D05 +) + +// ---- GL function pointers ---- + +var ( + gpGetString, + gpGetError, + gpBindTexture, + gpBindFramebuffer, + gpBindRenderbuffer, + gpTexParameteri, + gpTexImage2D, + gpGenTextures, + gpGenFramebuffers, + gpGenRenderbuffers, + gpFramebufferTexture2D, + gpFramebufferRenderbuffer, + gpRenderbufferStorage, + gpCheckFramebufferStatus, + gpDeleteFramebuffers, + gpDeleteRenderbuffers, + gpDeleteTextures, + gpReadPixels, + gpPixelStorei unsafe.Pointer +) + +type glFunc struct { + ptr *unsafe.Pointer + name string +} + +var glFuncs = [...]glFunc{ + {&gpGetString, "glGetString"}, + {&gpGetError, "glGetError"}, + {&gpBindTexture, "glBindTexture"}, + {&gpBindFramebuffer, "glBindFramebuffer"}, + {&gpBindRenderbuffer, "glBindRenderbuffer"}, + {&gpTexParameteri, "glTexParameteri"}, + {&gpTexImage2D, "glTexImage2D"}, + {&gpGenTextures, "glGenTextures"}, + {&gpGenFramebuffers, "glGenFramebuffers"}, + {&gpGenRenderbuffers, "glGenRenderbuffers"}, + {&gpFramebufferTexture2D, "glFramebufferTexture2D"}, + {&gpFramebufferRenderbuffer, "glFramebufferRenderbuffer"}, + {&gpRenderbufferStorage, "glRenderbufferStorage"}, + {&gpCheckFramebufferStatus, "glCheckFramebufferStatus"}, + {&gpDeleteFramebuffers, "glDeleteFramebuffers"}, + {&gpDeleteRenderbuffers, "glDeleteRenderbuffers"}, + {&gpDeleteTextures, "glDeleteTextures"}, + {&gpReadPixels, "glReadPixels"}, + {&gpPixelStorei, "glPixelStorei"}, +} + type Context int const ( @@ -31,6 +164,84 @@ const ( UnsignedInt8888Rev ) +type Config struct { + Ctx Context + W, H int + GLAutoContext bool + GLVersionMajor uint + GLVersionMinor uint + GLHasDepth bool + GLHasStencil bool +} + +func initProcAddr(getProcAddr func(name string) unsafe.Pointer) error { + for _, f := range glFuncs { + p := getProcAddr(f.name) + if p == nil { + return errors.New(f.name) + } + *f.ptr = p + } + return nil +} + +func goStr(s *uint8) string { return C.GoString((*C.char)(unsafe.Pointer(s))) } + +func getString(name uint32) *uint8 { + return (*uint8)(C.gl_get_string((C.GPGETSTRING)(gpGetString), C.GLenum(name))) +} +func genTextures(n int32, textures *uint32) { + C.gl_1u32((C.GPGENTEXTURES)(gpGenTextures), C.GLsizei(n), (*C.GLuint)(unsafe.Pointer(textures))) +} +func bindTexture(target, texture uint32) { + C.gl_bind_tex((C.GPBINDTEXTURE)(gpBindTexture), C.GLenum(target), C.GLuint(texture)) +} +func bindFramebuffer(target, framebuffer uint32) { + C.gl_bind_fb((C.GPBINDFRAMEBUFFER)(gpBindFramebuffer), C.GLenum(target), C.GLuint(framebuffer)) +} +func bindRenderbuffer(target, renderbuffer uint32) { + C.gl_bind_rb((C.GPBINDRENDERBUFFER)(gpBindRenderbuffer), C.GLenum(target), C.GLuint(renderbuffer)) +} +func texParameteri(target, pname uint32, param int32) { + C.gl_tex_param((C.GPTEXPARAMETERI)(gpTexParameteri), C.GLenum(target), C.GLenum(pname), C.GLint(param)) +} +func texImage2D(target uint32, level, internalformat, width, height, border int32, format, xtype uint32, pixels unsafe.Pointer) { + C.gl_tex_image((C.GPTEXIMAGE2D)(gpTexImage2D), C.GLenum(target), C.GLint(level), C.GLint(internalformat), C.GLsizei(width), C.GLsizei(height), C.GLint(border), C.GLenum(format), C.GLenum(xtype), pixels) +} +func genFramebuffers(n int32, framebuffers *uint32) { + C.gl_1u32((C.GPGENTEXTURES)(gpGenFramebuffers), C.GLsizei(n), (*C.GLuint)(unsafe.Pointer(framebuffers))) +} +func framebufferTexture2D(target, attachment, texTarget, texture uint32, level int32) { + C.gl_fb_tex2d((C.GPFRAMEBUFFERTEXTURE2D)(gpFramebufferTexture2D), C.GLenum(target), C.GLenum(attachment), C.GLenum(texTarget), C.GLuint(texture), C.GLint(level)) +} +func genRenderbuffers(n int32, renderbuffers *uint32) { + C.gl_1u32((C.GPGENTEXTURES)(gpGenRenderbuffers), C.GLsizei(n), (*C.GLuint)(unsafe.Pointer(renderbuffers))) +} +func renderbufferStorage(target, internalformat uint32, width, height int32) { + C.gl_rb_storage((C.GPRENDERBUFFERSTORAGE)(gpRenderbufferStorage), C.GLenum(target), C.GLenum(internalformat), C.GLsizei(width), C.GLsizei(height)) +} +func framebufferRenderbuffer(target, attachment, rbtarget, rb uint32) { + C.gl_fb_rb((C.GPFRAMEBUFFERRENDERBUFFER)(gpFramebufferRenderbuffer), C.GLenum(target), C.GLenum(attachment), C.GLenum(rbtarget), C.GLuint(rb)) +} +func checkFramebufferStatus(target uint32) uint32 { + return uint32(C.gl_check_fb_status((C.GPCHECKFRAMEBUFFERSTATUS)(gpCheckFramebufferStatus), C.GLenum(target))) +} +func deleteRenderbuffers(n int32, renderbuffers *uint32) { + C.gl_delete_rb((C.GPDELETERENDERBUFFERS)(gpDeleteRenderbuffers), C.GLsizei(n), (*C.GLuint)(unsafe.Pointer(renderbuffers))) +} +func deleteFramebuffers(n int32, framebuffers *uint32) { + C.gl_delete_fb((C.GPDELETEFRAMEBUFFERS)(gpDeleteFramebuffers), C.GLsizei(n), (*C.GLuint)(unsafe.Pointer(framebuffers))) +} +func deleteTextures(n int32, textures *uint32) { + C.gl_delete_tex((C.GPDELETETEXTURES)(gpDeleteTextures), C.GLsizei(n), (*C.GLuint)(unsafe.Pointer(textures))) +} +func readPixels(x, y, width, height int32, format, xtype uint32, pixels unsafe.Pointer) { + C.gl_read_pixels((C.GPREADPIXELS)(gpReadPixels), C.GLint(x), C.GLint(y), C.GLsizei(width), C.GLsizei(height), C.GLenum(format), C.GLenum(xtype), pixels) +} +func pixelStorei(pname uint32, param int32) { + C.gl_pixel_storei((C.GPPIXELSTOREI)(gpPixelStorei), C.GLenum(pname), C.GLint(param)) +} + var ( fbo, tex, rbo uint32 hasDepth bool @@ -40,40 +251,40 @@ var ( ) func initContext(getProcAddr func(name string) unsafe.Pointer) { - if err := gl.InitWithProcAddrFunc(getProcAddr); err != nil { + if err := initProcAddr(getProcAddr); err != nil { panic(err) } - gl.PixelStorei(gl.PackAlignment, 1) + pixelStorei(gl_PACK_ALIGNMENT, 1) } 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, w, h, 0, pixType, pixFormat, nil) - gl.BindTexture(gl.Texture2d, 0) + genTextures(1, &tex) + bindTexture(gl_TEXTURE_2D, tex) + texParameteri(gl_TEXTURE_2D, gl_TEXTURE_MIN_FILTER, gl_NEAREST) + texParameteri(gl_TEXTURE_2D, gl_TEXTURE_MAG_FILTER, gl_NEAREST) + texImage2D(gl_TEXTURE_2D, 0, gl_RGBA8, w, h, 0, pixType, pixFormat, nil) + bindTexture(gl_TEXTURE_2D, 0) - gl.GenFramebuffers(1, &fbo) - gl.BindFramebuffer(gl.FRAMEBUFFER, fbo) - gl.FramebufferTexture2D(gl.FRAMEBUFFER, gl.ColorAttachment0, gl.Texture2d, tex, 0) + genFramebuffers(1, &fbo) + bindFramebuffer(gl_FRAMEBUFFER, fbo) + framebufferTexture2D(gl_FRAMEBUFFER, gl_COLOR_ATTACHMENT0, gl_TEXTURE_2D, tex, 0) if depth { - gl.GenRenderbuffers(1, &rbo) - gl.BindRenderbuffer(gl.RENDERBUFFER, rbo) - format, attachment := uint32(gl.DepthComponent24), uint32(gl.DepthAttachment) + genRenderbuffers(1, &rbo) + bindRenderbuffer(gl_RENDERBUFFER, rbo) + format, attachment := uint32(gl_DEPTH_COMPONENT24), uint32(gl_DEPTH_ATTACHMENT) if stencil { - format, attachment = gl.Depth24Stencil8, gl.DepthStencilAttachment + format, attachment = gl_DEPTH24_STENCIL8, gl_DEPTH_STENCIL_ATTACH } - gl.RenderbufferStorage(gl.RENDERBUFFER, format, w, h) - gl.FramebufferRenderbuffer(gl.FRAMEBUFFER, attachment, gl.RENDERBUFFER, rbo) - gl.BindRenderbuffer(gl.RENDERBUFFER, 0) + renderbufferStorage(gl_RENDERBUFFER, format, w, h) + framebufferRenderbuffer(gl_FRAMEBUFFER, attachment, gl_RENDERBUFFER, rbo) + bindRenderbuffer(gl_RENDERBUFFER, 0) } - if status := gl.CheckFramebufferStatus(gl.FRAMEBUFFER); status != gl.FramebufferComplete { + if status := checkFramebufferStatus(gl_FRAMEBUFFER); status != gl_FRAMEBUFFER_COMPLETE { return fmt.Errorf("framebuffer incomplete: 0x%X", status) } return nil @@ -81,15 +292,17 @@ func initFramebuffer(width, height int, depth, stencil bool) error { func destroyFramebuffer() { if hasDepth { - gl.DeleteRenderbuffers(1, &rbo) + deleteRenderbuffers(1, &rbo) } - gl.DeleteFramebuffers(1, &fbo) - gl.DeleteTextures(1, &tex) + deleteFramebuffers(1, &fbo) + deleteTextures(1, &tex) } +// ReadFramebuffer reads the current FBO color attachment into the internal buffer. +// Call SetBuffer first to size the buffer (w * h * 4). func ReadFramebuffer(size, w, h uint) []byte { - gl.BindFramebuffer(gl.FRAMEBUFFER, fbo) - gl.ReadPixels(0, 0, int32(w), int32(h), pixType, pixFormat, bufPtr) + bindFramebuffer(gl_FRAMEBUFFER, fbo) + readPixels(0, 0, int32(w), int32(h), pixType, pixFormat, bufPtr) return buf[:size] } @@ -101,11 +314,11 @@ func SetBuffer(size int) { func SetPixelFormat(format PixelFormat) error { switch format { case UnsignedShort5551: - pixFormat, pixType = gl.UnsignedShort5551, gl.BGRA + pixFormat, pixType = 0x8034, gl_BGRA // GL_UNSIGNED_SHORT_5_5_5_1 case UnsignedShort565: - pixFormat, pixType = gl.UnsignedShort565, gl.RGB + pixFormat, pixType = 0x8363, gl_RGB // GL_UNSIGNED_SHORT_5_6_5 case UnsignedInt8888Rev: - pixFormat, pixType = gl.UnsignedInt8888Rev, gl.BGRA + pixFormat, pixType = 0x8367, gl_BGRA // GL_UNSIGNED_INT_8_8_8_8_REV default: return errors.New("unknown pixel format") } @@ -113,10 +326,10 @@ func SetPixelFormat(format PixelFormat) error { } 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)) + return goStr(getString(gl_VERSION)), + goStr(getString(gl_VENDOR)), + goStr(getString(gl_RENDERER)), + goStr(getString(gl_SHADING_LANG_VERSION)) } func GlFbo() uint32 { return fbo } diff --git a/pkg/worker/caged/libretro/graphics/rgfw.go b/pkg/worker/caged/libretro/graphics/rgfw.go new file mode 100644 index 00000000..ce2471b4 --- /dev/null +++ b/pkg/worker/caged/libretro/graphics/rgfw.go @@ -0,0 +1,575 @@ +package graphics + +/* +#cgo windows LDFLAGS: -lgdi32 -lopengl32 +#cgo linux LDFLAGS: -lX11 -lGL +#cgo darwin LDFLAGS: -framework OpenGL + +// ========================================================================== +// rgfw_nano — minimal window + GL context for cloud-game +// Derived from RGFW 2.0.0-dev (libpng license) +// Copyright (C) 2022-26 Riley Mabb (@ColleagueRiley) +// Altered: stripped to ~540 lines from ~16,400 +// Original: https://github.com/ColleagueRiley/RGFW +// ========================================================================== + +#include +#include +#include + +// ---- platform detection ---- +#if defined(_WIN32) + #define RGFW_WINDOWS + #include +#elif defined(__linux__) || defined(__FreeBSD__) || defined(__OpenBSD__) + #define RGFW_X11 + #include + #include + #include +#elif defined(__APPLE__) + #define RGFW_MACOS + #include + #include + #include +#endif + +// ---- basic types ---- +typedef unsigned char u8; +typedef signed char i8; +typedef unsigned short u16; +typedef signed short i16; +typedef unsigned int u32; +typedef signed int i32; +typedef unsigned long long u64; +typedef signed long long i64; +typedef u8 RGFW_bool; +#define RGFW_TRUE 1 +#define RGFW_FALSE 0 + +#define RGFW_ALLOC malloc +#define RGFW_FREE free + +// ---- window flags ---- +typedef u32 RGFW_windowFlags; +#define RGFW_windowHide (1u << 9) + +// ---- GL types ---- +typedef void (*RGFW_proc)(void); + +typedef struct RGFW_glContext { +#ifdef RGFW_WINDOWS + HGLRC ctx; +#elif defined(RGFW_MACOS) + void* ctx; +#else + GLXContext ctx; +#endif +} RGFW_glContext; + +typedef enum { RGFW_glCore = 0, RGFW_glForwardCompatibility, RGFW_glCompatibility, RGFW_glES } RGFW_glProfile; +typedef enum { RGFW_glAccelerated = 0, RGFW_glSoftware } RGFW_glRenderer; +typedef enum { RGFW_glReleaseNone = 0 } RGFW_glReleaseBehavior; + +typedef struct RGFW_glHints { + i32 stencil, samples, stereo, auxBuffers, doubleBuffer; + i32 red, green, blue, alpha, depth; + i32 accumRed, accumGreen, accumBlue, accumAlpha; + RGFW_bool sRGB, robustness, debug, noError; + RGFW_glReleaseBehavior releaseBehavior; + RGFW_glProfile profile; + i32 major, minor; + RGFW_glContext* share; + RGFW_glRenderer renderer; +} RGFW_glHints; + +typedef u32 RGFW_gfxContextType; +#define RGFW_gfxNativeOpenGL (1u << 0) +#define RGFW_gfxOwnedByRGFW (1u << 2) + +// ---- window struct ---- +#ifdef RGFW_WINDOWS +typedef struct RGFW_window_src { + HWND window; + HDC hdc; + RGFW_glContext* ctx_native; + RGFW_gfxContextType gfxType; +} RGFW_window_src; +#elif defined(RGFW_MACOS) +typedef struct RGFW_window_src { + void* window; + void* view; + RGFW_glContext* ctx_native; + RGFW_gfxContextType gfxType; +} RGFW_window_src; +#else +typedef struct RGFW_window_src { + Window window; + Display* display; + Colormap cmap; + GLXFBConfig fbconfig; + RGFW_glContext* ctx_native; + RGFW_gfxContextType gfxType; +} RGFW_window_src; +#endif + +typedef struct RGFW_window { + RGFW_window_src src; + i32 x, y, w, h; +} RGFW_window; + +// ---- global hints ---- +static RGFW_glHints RGFW_nano_hints = {0,0,0,0,1, 8,8,8,8,24, 0,0,0,0, 0,0,0,0, RGFW_glReleaseNone, RGFW_glCore, 1,0, NULL, RGFW_glAccelerated}; + +static void RGFW_resetGlobalHints_OpenGL(void) { + RGFW_glHints d = {0,0,0,0,1, 8,8,8,8,24, 0,0,0,0, 0,0,0,0, RGFW_glReleaseNone, RGFW_glCore, 1,0, NULL, RGFW_glAccelerated}; + RGFW_nano_hints = d; +} +static void RGFW_setGlobalHints_OpenGL(RGFW_glHints* h) { RGFW_nano_hints = *h; } +static RGFW_glHints* RGFW_getGlobalHints_OpenGL(void) { return &RGFW_nano_hints; } + +// ---- API prototypes ---- +static i32 RGFW_init(const char* name, u8 flags); +static void RGFW_deinit(void); +static RGFW_window* RGFW_createWindow(const char* name, i32 x, i32 y, i32 w, i32 h, RGFW_windowFlags flags); +static void RGFW_window_close(RGFW_window* win); +static void RGFW_window_setName(RGFW_window* win, const char* name); +static void RGFW_window_makeCurrentContext_OpenGL(RGFW_window* win); +static RGFW_proc RGFW_getProcAddress_OpenGL(const char* name); +static RGFW_bool RGFW_window_createContextPtr_OpenGL(RGFW_window* win, RGFW_glContext* ctx, RGFW_glHints* hints); +static RGFW_glContext* RGFW_window_createContext_OpenGL(RGFW_window* win, RGFW_glHints* hints); + +// ========================================================================== +// Platform implementations +// ========================================================================== + +#ifdef RGFW_WINDOWS +// ------------------------------------------------------------------- +// Windows (Win32 + WGL) +// ------------------------------------------------------------------- + +static LRESULT CALLBACK RGFW_nano_WndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) { + return DefWindowProcW(hwnd, msg, wp, lp); +} + +static i32 RGFW_init(const char* name, u8 flags) { (void)name; (void)flags; return 0; } +static void RGFW_deinit(void) {} + +static RGFW_window* RGFW_createWindow(const char* name, i32 x, i32 y, i32 w, i32 h, RGFW_windowFlags flags) { + (void)x; (void)y; + RGFW_window* win = (RGFW_window*)RGFW_ALLOC(sizeof(RGFW_window)); + if (!win) return NULL; + memset(win, 0, sizeof(*win)); + win->w = w; win->h = h; + + HINSTANCE hi = GetModuleHandleW(NULL); + WNDCLASSW wc = {0}; + wc.lpfnWndProc = RGFW_nano_WndProc; + wc.hInstance = hi; + wc.lpszClassName = L"rgfw_nano_wc"; + RegisterClassW(&wc); + + DWORD style = WS_CLIPSIBLINGS | WS_CLIPCHILDREN; + RECT rect = {0, 0, (LONG)w, (LONG)h}; + AdjustWindowRect(&rect, style, FALSE); + + win->src.window = CreateWindowExW(0, L"rgfw_nano_wc", L"", + style, 0, 0, rect.right - rect.left, rect.bottom - rect.top, + NULL, NULL, hi, NULL); + if (!win->src.window) { RGFW_FREE(win); return NULL; } + + win->src.hdc = GetDC(win->src.window); + return win; +} + +static void RGFW_window_close(RGFW_window* win) { + if (!win) return; + if (win->src.ctx_native && (win->src.gfxType & RGFW_gfxOwnedByRGFW)) + RGFW_FREE(win->src.ctx_native); + if (win->src.hdc) { ReleaseDC(win->src.window, win->src.hdc); win->src.hdc = NULL; } + if (win->src.window) { DestroyWindow(win->src.window); win->src.window = NULL; } + RGFW_FREE(win); +} + +static void RGFW_window_setName(RGFW_window* win, const char* name) { (void)win; (void)name; } + +static void RGFW_window_makeCurrentContext_OpenGL(RGFW_window* win) { + if (win && win->src.hdc && win->src.ctx_native && win->src.ctx_native->ctx) + wglMakeCurrent(win->src.hdc, win->src.ctx_native->ctx); +} + +static RGFW_proc RGFW_getProcAddress_OpenGL(const char* name) { + static HMODULE glModule; + if (!glModule) glModule = LoadLibraryA("opengl32.dll"); + if (glModule) { RGFW_proc p = (RGFW_proc)GetProcAddress(glModule, name); if (p) return p; } + return (RGFW_proc)wglGetProcAddress(name); +} + +static RGFW_bool RGFW_window_createContextPtr_OpenGL(RGFW_window* win, RGFW_glContext* ctx, RGFW_glHints* hints) { + (void)hints; + if (!win || !win->src.hdc || !ctx) return RGFW_FALSE; + + PIXELFORMATDESCRIPTOR pfd = {0}; + pfd.nSize = sizeof(PIXELFORMATDESCRIPTOR); + pfd.nVersion = 1; + pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER | PFD_GENERIC_FORMAT; + pfd.iPixelType = PFD_TYPE_RGBA; + pfd.cColorBits = 32; + pfd.cDepthBits = 24; + pfd.cStencilBits = 8; + pfd.iLayerType = PFD_MAIN_PLANE; + + int pf = ChoosePixelFormat(win->src.hdc, &pfd); + if (!pf || !SetPixelFormat(win->src.hdc, pf, &pfd)) return RGFW_FALSE; + + ctx->ctx = wglCreateContext(win->src.hdc); + if (!ctx->ctx) return RGFW_FALSE; + + win->src.ctx_native = ctx; + win->src.gfxType = RGFW_gfxNativeOpenGL; + return RGFW_TRUE; +} + +static RGFW_glContext* RGFW_window_createContext_OpenGL(RGFW_window* win, RGFW_glHints* hints) { + RGFW_glContext* ctx = (RGFW_glContext*)RGFW_ALLOC(sizeof(RGFW_glContext)); + if (!ctx) return NULL; + if (!RGFW_window_createContextPtr_OpenGL(win, ctx, hints)) { RGFW_FREE(ctx); return NULL; } + win->src.gfxType |= RGFW_gfxOwnedByRGFW; + return ctx; +} + +// ------------------------------------------------------------------- +// macOS (CGL, offscreen) +// ------------------------------------------------------------------- +#elif defined(RGFW_MACOS) + +static i32 RGFW_init(const char* name, u8 flags) { (void)name; (void)flags; return 0; } +static void RGFW_deinit(void) {} + +static RGFW_window* RGFW_createWindow(const char* name, i32 x, i32 y, i32 w, i32 h, RGFW_windowFlags flags) { + (void)x; (void)y; (void)flags; (void)name; + RGFW_window* win = (RGFW_window*)RGFW_ALLOC(sizeof(RGFW_window)); + if (!win) return NULL; + memset(win, 0, sizeof(*win)); + win->w = w; win->h = h; + return win; +} + +static void RGFW_window_close(RGFW_window* win) { + if (!win) return; + if (win->src.ctx_native && (win->src.gfxType & RGFW_gfxNativeOpenGL)) { + if (win->src.ctx_native->ctx) CGLDestroyContext((CGLContextObj)win->src.ctx_native->ctx); + if (win->src.gfxType & RGFW_gfxOwnedByRGFW) RGFW_FREE(win->src.ctx_native); + } + RGFW_FREE(win); +} + +static void RGFW_window_setName(RGFW_window* win, const char* name) { (void)win; (void)name; } + +static void RGFW_window_makeCurrentContext_OpenGL(RGFW_window* win) { + if (win && win->src.ctx_native && win->src.ctx_native->ctx) + CGLSetCurrentContext((CGLContextObj)win->src.ctx_native->ctx); +} + +static RGFW_proc RGFW_getProcAddress_OpenGL(const char* name) { + static void* gl_handle; + if (!gl_handle) gl_handle = dlopen("/System/Library/Frameworks/OpenGL.framework/OpenGL", RTLD_LAZY); + if (gl_handle) { void* sym = dlsym(gl_handle, name); if (sym) return (RGFW_proc)sym; } + return NULL; +} + +static RGFW_bool RGFW_window_createContextPtr_OpenGL(RGFW_window* win, RGFW_glContext* ctx, RGFW_glHints* hints) { + (void)win; + if (!ctx) return RGFW_FALSE; + + CGLPixelFormatAttribute attrs[32]; + int n = 0; + attrs[n++] = kCGLPFAOpenGLProfile; + attrs[n++] = (CGLPixelFormatAttribute)kCGLOGLPVersion_Legacy; + if (hints && hints->renderer == RGFW_glAccelerated) attrs[n++] = kCGLPFAAccelerated; + attrs[n++] = kCGLPFAColorSize; attrs[n++] = 32; + attrs[n++] = kCGLPFADepthSize; attrs[n++] = hints ? hints->depth : 24; + attrs[n++] = kCGLPFAStencilSize; attrs[n++] = hints ? hints->stencil : 8; + if (hints && hints->doubleBuffer) attrs[n++] = kCGLPFADoubleBuffer; + attrs[n++] = 0; + + CGLPixelFormatObj pix = NULL; GLint npix = 0; + if (CGLChoosePixelFormat(attrs, &pix, &npix) != kCGLNoError || !pix) return RGFW_FALSE; + + CGLError err = CGLCreateContext(pix, NULL, (CGLContextObj*)&ctx->ctx); + CGLDestroyPixelFormat(pix); + if (err != kCGLNoError || !ctx->ctx) return RGFW_FALSE; + + if (CGLSetCurrentContext((CGLContextObj)ctx->ctx) != kCGLNoError) { + CGLDestroyContext((CGLContextObj)ctx->ctx); ctx->ctx = NULL; return RGFW_FALSE; + } + + win->src.ctx_native = ctx; + win->src.gfxType = RGFW_gfxNativeOpenGL; + return RGFW_TRUE; +} + +static RGFW_glContext* RGFW_window_createContext_OpenGL(RGFW_window* win, RGFW_glHints* hints) { + RGFW_glContext* ctx = (RGFW_glContext*)RGFW_ALLOC(sizeof(RGFW_glContext)); + if (!ctx) return NULL; + if (!RGFW_window_createContextPtr_OpenGL(win, ctx, hints)) { RGFW_FREE(ctx); return NULL; } + win->src.gfxType |= RGFW_gfxOwnedByRGFW; + return ctx; +} + +// ------------------------------------------------------------------- +// X11 (GLX) +// ------------------------------------------------------------------- +#else + +static Display* RGFW_nano_display = NULL; +static Atom RGFW_nano_WM_DELETE_WINDOW; + +static i32 RGFW_init(const char* name, u8 flags) { + (void)name; (void)flags; + RGFW_nano_display = XOpenDisplay(NULL); + if (!RGFW_nano_display) return -1; + RGFW_nano_WM_DELETE_WINDOW = XInternAtom(RGFW_nano_display, "WM_DELETE_WINDOW", False); + return 0; +} + +static void RGFW_deinit(void) { + if (RGFW_nano_display) { XCloseDisplay(RGFW_nano_display); RGFW_nano_display = NULL; } +} + +static RGFW_window* RGFW_createWindow(const char* name, i32 x, i32 y, i32 w, i32 h, RGFW_windowFlags flags) { + (void)x; (void)y; (void)flags; + if (!RGFW_nano_display) return NULL; + + RGFW_window* win = (RGFW_window*)RGFW_ALLOC(sizeof(RGFW_window)); + if (!win) return NULL; + memset(win, 0, sizeof(*win)); + win->w = w; win->h = h; + win->src.display = RGFW_nano_display; + + int scr = DefaultScreen(RGFW_nano_display); + Window root = DefaultRootWindow(RGFW_nano_display); + + XSetWindowAttributes swa; + swa.event_mask = StructureNotifyMask | ExposureMask; + swa.colormap = XCreateColormap(RGFW_nano_display, root, DefaultVisual(RGFW_nano_display, scr), AllocNone); + win->src.cmap = swa.colormap; + + win->src.window = XCreateWindow(RGFW_nano_display, root, 0, 0, w, h, 0, + DefaultDepth(RGFW_nano_display, scr), InputOutput, + DefaultVisual(RGFW_nano_display, scr), CWEventMask | CWColormap, &swa); + if (!win->src.window) { RGFW_FREE(win); return NULL; } + + XSetWMProtocols(RGFW_nano_display, win->src.window, &RGFW_nano_WM_DELETE_WINDOW, 1); + RGFW_window_setName(win, name); + XMapWindow(RGFW_nano_display, win->src.window); + XFlush(RGFW_nano_display); + return win; +} + +static void RGFW_window_close(RGFW_window* win) { + if (!win) return; + if (win->src.ctx_native && (win->src.gfxType & RGFW_gfxNativeOpenGL)) { + glXMakeCurrent(win->src.display, None, NULL); + if (win->src.ctx_native->ctx) glXDestroyContext(win->src.display, win->src.ctx_native->ctx); + if (win->src.gfxType & RGFW_gfxOwnedByRGFW) RGFW_FREE(win->src.ctx_native); + } + if (win->src.window) { XDestroyWindow(win->src.display, win->src.window); win->src.window = 0; } + if (win->src.cmap) { XFreeColormap(win->src.display, win->src.cmap); win->src.cmap = 0; } + RGFW_FREE(win); +} + +static void RGFW_window_setName(RGFW_window* win, const char* name) { + if (win && win->src.display && win->src.window) XStoreName(win->src.display, win->src.window, name); +} + +static void RGFW_window_makeCurrentContext_OpenGL(RGFW_window* win) { + if (win && win->src.display && win->src.ctx_native && win->src.ctx_native->ctx) + glXMakeCurrent(win->src.display, win->src.window, win->src.ctx_native->ctx); +} + +static RGFW_proc RGFW_getProcAddress_OpenGL(const char* name) { + return (RGFW_proc)glXGetProcAddress((const GLubyte*)name); +} + +static int RGFW_nano_glx_attribs(int* attribs, int max, RGFW_glHints* hints) { + int n = 0; + (void)max; + attribs[n++] = GLX_RENDER_TYPE; attribs[n++] = GLX_RGBA_BIT; + attribs[n++] = GLX_DRAWABLE_TYPE; attribs[n++] = GLX_WINDOW_BIT; + attribs[n++] = GLX_DOUBLEBUFFER; attribs[n++] = hints->doubleBuffer ? True : False; + attribs[n++] = GLX_RED_SIZE; attribs[n++] = hints->red; + attribs[n++] = GLX_GREEN_SIZE; attribs[n++] = hints->green; + attribs[n++] = GLX_BLUE_SIZE; attribs[n++] = hints->blue; + attribs[n++] = GLX_ALPHA_SIZE; attribs[n++] = hints->alpha; + attribs[n++] = GLX_DEPTH_SIZE; attribs[n++] = hints->depth; + attribs[n++] = GLX_STENCIL_SIZE; attribs[n++] = hints->stencil; + attribs[n++] = None; + return n; +} + +static RGFW_bool RGFW_window_createContextPtr_OpenGL(RGFW_window* win, RGFW_glContext* ctx, RGFW_glHints* hints) { + if (!win || !win->src.display || !ctx) return RGFW_FALSE; + + int attribs[64]; + RGFW_nano_glx_attribs(attribs, 64, hints); + + int fbcount; + GLXFBConfig* fbcs = glXChooseFBConfig(win->src.display, DefaultScreen(win->src.display), attribs, &fbcount); + if (!fbcs || fbcount == 0) return RGFW_FALSE; + + win->src.fbconfig = fbcs[0]; + XFree(fbcs); + + GLXContext share = (hints && hints->share) ? hints->share->ctx : NULL; + + typedef GLXContext (*glXCreateContextAttribsARBProc)(Display*, GLXFBConfig, GLXContext, Bool, const int*); + glXCreateContextAttribsARBProc glXCreateContextAttribsARB = + (glXCreateContextAttribsARBProc)glXGetProcAddress((const GLubyte*)"glXCreateContextAttribsARB"); + + if (glXCreateContextAttribsARB && hints && (hints->major > 0 || hints->minor > 0)) { + int ctx_attribs[] = { + GLX_CONTEXT_MAJOR_VERSION_ARB, hints->major, + GLX_CONTEXT_MINOR_VERSION_ARB, hints->minor, + GLX_CONTEXT_PROFILE_MASK_ARB, + hints->profile == RGFW_glCore ? GLX_CONTEXT_CORE_PROFILE_BIT_ARB : + hints->profile == RGFW_glCompatibility ? GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB : + GLX_CONTEXT_ES_PROFILE_BIT_EXT, + None + }; + ctx->ctx = glXCreateContextAttribsARB(win->src.display, win->src.fbconfig, share, True, ctx_attribs); + } + if (!ctx->ctx) { + ctx->ctx = glXCreateNewContext(win->src.display, win->src.fbconfig, GLX_RGBA_TYPE, share, True); + } + if (!ctx->ctx) return RGFW_FALSE; + + win->src.ctx_native = ctx; + win->src.gfxType = RGFW_gfxNativeOpenGL; + return RGFW_TRUE; +} + +static RGFW_glContext* RGFW_window_createContext_OpenGL(RGFW_window* win, RGFW_glHints* hints) { + RGFW_glContext* ctx = (RGFW_glContext*)RGFW_ALLOC(sizeof(RGFW_glContext)); + if (!ctx) return NULL; + if (!RGFW_window_createContextPtr_OpenGL(win, ctx, hints)) { RGFW_FREE(ctx); return NULL; } + win->src.gfxType |= RGFW_gfxOwnedByRGFW; + return ctx; +} +#endif +*/ +import "C" +import ( + "fmt" + "unsafe" +) + +// RGFW is a graphics context backed by the inlined rgfw_nano C code. +type RGFW struct { + win *C.RGFW_window + inited bool +} + +// NewRGFWContext creates a hidden window with an OpenGL context and an FBO. +func NewRGFWContext(cfg Config) (*RGFW, error) { + name := C.CString("cloud-retro") + defer C.free(unsafe.Pointer(name)) + + if ret := C.RGFW_init(name, 0); ret < 0 { + return nil, fmt.Errorf("rgfw init failed: %d", int(ret)) + } + + if !cfg.GLAutoContext { + var hints C.RGFW_glHints + C.RGFW_resetGlobalHints_OpenGL() + hints.major = C.i32(cfg.GLVersionMajor) + hints.minor = C.i32(cfg.GLVersionMinor) + if cfg.GLHasDepth { + hints.depth = 24 + } + if cfg.GLHasStencil { + hints.stencil = 8 + } + hints.doubleBuffer = 1 + hints.renderer = C.RGFW_glSoftware + + switch cfg.Ctx { + case CtxOpenGlCore: + hints.profile = C.RGFW_glCore + case CtxOpenGlEs2, CtxOpenGlEs3, CtxOpenGlEsVersion: + hints.profile = C.RGFW_glES + hints.major = 3 + hints.minor = 0 + case CtxOpenGl: + hints.profile = C.RGFW_glCompatibility + default: + hints.profile = C.RGFW_glCompatibility + } + C.RGFW_setGlobalHints_OpenGL(&hints) + } + + wname := C.CString("cloud-retro") + defer C.free(unsafe.Pointer(wname)) + win := C.RGFW_createWindow(wname, 0, 0, 1, 1, C.RGFW_windowHide) + if win == nil { + C.RGFW_deinit() + return nil, fmt.Errorf("rgfw window creation failed") + } + + if C.RGFW_window_createContext_OpenGL(win, C.RGFW_getGlobalHints_OpenGL()) == nil { + C.RGFW_window_close(win) + C.RGFW_deinit() + return nil, fmt.Errorf("rgfw GL context creation failed") + } + + C.RGFW_window_makeCurrentContext_OpenGL(win) + + initContext(func(name string) unsafe.Pointer { + cname := C.CString(name) + defer C.free(unsafe.Pointer(cname)) + return unsafe.Pointer(C.RGFW_getProcAddress_OpenGL(cname)) + }) + + if err := initFramebuffer(cfg.W, cfg.H, cfg.GLHasDepth, cfg.GLHasStencil); err != nil { + C.RGFW_window_close(win) + C.RGFW_deinit() + return nil, fmt.Errorf("rgfw fbo: %w", err) + } + + return &RGFW{win: win, inited: true}, nil +} + +func (r *RGFW) Deinit() error { + if !r.inited { + return nil + } + r.inited = false + destroyFramebuffer() + C.RGFW_window_close(r.win) + r.win = nil + C.RGFW_deinit() + return nil +} + +func (r *RGFW) BindContext() error { + if !r.inited { + return fmt.Errorf("rgfw: context not initialized") + } + C.RGFW_window_makeCurrentContext_OpenGL(r.win) + return nil +} + +func RGFWGlProcAddress(proc string) unsafe.Pointer { + cname := C.CString(proc) + defer C.free(unsafe.Pointer(cname)) + return unsafe.Pointer(C.RGFW_getProcAddress_OpenGL(cname)) +} + +func RGFWTryInit() error { + className := C.CString("cloud-retro-test") + defer C.free(unsafe.Pointer(className)) + if ret := C.RGFW_init(className, 0); ret < 0 { + return fmt.Errorf("rgfw init failed: %d", int(ret)) + } + C.RGFW_deinit() + return nil +} diff --git a/pkg/worker/caged/libretro/graphics/sdl.go b/pkg/worker/caged/libretro/graphics/sdl.go deleted file mode 100644 index 7c885d88..00000000 --- a/pkg/worker/caged/libretro/graphics/sdl.go +++ /dev/null @@ -1,100 +0,0 @@ -package graphics - -import ( - "fmt" - "unsafe" - - "github.com/veandco/go-sdl2/sdl" -) - -type SDL struct { - w *sdl.Window - ctx sdl.GLContext -} - -type Config struct { - Ctx Context - W, H int - GLAutoContext bool - GLVersionMajor uint - GLVersionMinor uint - GLHasDepth bool - GLHasStencil bool -} - -func NewSDLContext(cfg Config) (*SDL, error) { - if err := sdl.Init(sdl.INIT_VIDEO); err != nil { - return nil, fmt.Errorf("sdl: %w", err) - } - - if !cfg.GLAutoContext { - if err := setGLAttrs(cfg.Ctx); err != nil { - return nil, err - } - } - - 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: %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("fbo: %w", err) - } - - return &SDL{w: w, ctx: ctx}, nil -} - -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 err - } - sdl.Quit() - return nil -} diff --git a/pkg/worker/caged/libretro/nanoarch/nanoarch.go b/pkg/worker/caged/libretro/nanoarch/nanoarch.go index eb6cd4ce..079702d9 100644 --- a/pkg/worker/caged/libretro/nanoarch/nanoarch.go +++ b/pkg/worker/caged/libretro/nanoarch/nanoarch.go @@ -68,7 +68,7 @@ type Nanoarch struct { } vfr bool Aspect bool - sdlCtx *graphics.SDL + glCtx *graphics.RGFW hackSkipHwContextDestroy bool hackSkipSameThreadSave bool limiter func(func()) @@ -373,7 +373,7 @@ func (n *Nanoarch) Shutdown() { thread.Main(func() { // running inside a go routine, lock the thread to make sure the OpenGL context stays current runtime.LockOSThread() - if err := n.sdlCtx.BindContext(); err != nil { + if err := n.glCtx.BindContext(); err != nil { n.log.Error().Err(err).Msg("ctx switch fail") } }) @@ -421,7 +421,7 @@ func (n *Nanoarch) Run() { } else { if n.Video.gl.enabled { runtime.LockOSThread() - if err := n.sdlCtx.BindContext(); err != nil { + if err := n.glCtx.BindContext(); err != nil { n.log.Error().Err(err).Msg("ctx bind fail") } } @@ -432,7 +432,7 @@ func (n *Nanoarch) Run() { } } -func (n *Nanoarch) IsSupported() error { return graphics.TryInit() } +func (n *Nanoarch) IsSupported() error { return graphics.RGFWTryInit() } func (n *Nanoarch) IsGL() bool { return n.Video.gl.enabled } func (n *Nanoarch) IsStopped() bool { return n.Stopped.Load() } func (n *Nanoarch) InputRetropad(port int, data []byte) { n.retropad.SetInput(port, data) } @@ -744,7 +744,7 @@ func coreGetCurrentFramebuffer() C.uintptr_t { return (C.uintptr_t)(graphics.GlF //export coreGetProcAddress func coreGetProcAddress(sym *C.char) C.retro_proc_address_t { - return (C.retro_proc_address_t)(graphics.GlProcAddress(C.GoString(sym))) + return (C.retro_proc_address_t)(graphics.RGFWGlProcAddress(C.GoString(sym))) } //export coreEnvironment @@ -903,7 +903,7 @@ func initVideo() { thread.Main(func() { var err error - Nan0.sdlCtx, err = graphics.NewSDLContext(graphics.Config{ + Nan0.glCtx, err = graphics.NewRGFWContext(graphics.Config{ Ctx: context, W: int(Nan0.sys.av.geometry.max_width), H: int(Nan0.sys.av.geometry.max_height), @@ -929,7 +929,7 @@ func deinitVideo() { C.bridge_context_reset(Nan0.Video.hw.context_destroy) } thread.Main(func() { - if err := Nan0.sdlCtx.Deinit(); err != nil { + if err := Nan0.glCtx.Deinit(); err != nil { Nan0.log.Error().Err(err).Msg("deinit fail") } })