mirror of
https://github.com/libretro/ludo.git
synced 2026-11-08 08:09:44 -10:00
Compare commits
76
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1337ec455c | ||
|
|
36a07d8fd6 | ||
|
|
33e3d215e6 | ||
|
|
920201f5f7 | ||
|
|
c242440b06 | ||
|
|
36e74260a2 | ||
|
|
52d1455923 | ||
|
|
066b8af747 | ||
|
|
fa026a036c | ||
|
|
5c4c2637ce | ||
|
|
c68bffcaa2 | ||
|
|
15b47eaf0d | ||
|
|
6c53b45b40 | ||
|
|
bd0e8860f5 | ||
|
|
c6e310c3c4 | ||
|
|
80c6a1ac13 | ||
|
|
054b69690c | ||
|
|
3edf65f9fd | ||
|
|
e01cc6bb86 | ||
|
|
3e084bf7f1 | ||
|
|
2bcbb1d24c | ||
|
|
7d48bb840b | ||
|
|
cb8649a129 | ||
|
|
d74969f50e | ||
|
|
a95767339e | ||
|
|
346dbd8a33 | ||
|
|
c95a3a7a14 | ||
|
|
8647e6f893 | ||
|
|
a43843bab1 | ||
|
|
6097d396df | ||
|
|
e4c5845b57 | ||
|
|
da0092ef53 | ||
|
|
d8afc6031a | ||
|
|
1e0c4a0966 | ||
|
|
e01a76b08d | ||
|
|
73d70cb1f9 | ||
|
|
8b8d49eef4 | ||
|
|
008a70c851 | ||
|
|
1d8f8e8fac | ||
|
|
1146cb6f99 | ||
|
|
dbfbe3ace5 | ||
|
|
e35cba220c | ||
|
|
f32af42ba9 | ||
|
|
827ae5461d | ||
|
|
40396a4b86 | ||
|
|
ff9a3c8f4b | ||
|
|
b8144699f6 | ||
|
|
42d43d63c3 | ||
|
|
9c5c3f5962 | ||
|
|
2dd9150510 | ||
|
|
d127589d50 | ||
|
|
05adbe4f6a | ||
|
|
ab13ef0ebc | ||
|
|
8f51dd3b2d | ||
|
|
4618fb63bf | ||
|
|
2d55e97431 | ||
|
|
c7030756e3 | ||
|
|
614c5c2ed9 | ||
|
|
466c2ef696 | ||
|
|
4914b16606 | ||
|
|
341ad35519 | ||
|
|
933c86b852 | ||
|
|
681dfbc6ab | ||
|
|
f241ef4995 | ||
|
|
6e4b4b634e | ||
|
|
87175ca350 | ||
|
|
7eef38cf2e | ||
|
|
dc56ebfff9 | ||
|
|
d3fd407e77 | ||
|
|
a7cf8d1f99 | ||
|
|
a7da8f7cb9 | ||
|
|
967b0da04f | ||
|
|
c6c0c9c819 | ||
|
|
d41ab5d9d7 | ||
|
|
7e794694f5 | ||
|
|
d861e3d47d |
No files matched your search
@@ -11,22 +11,19 @@ ifeq ($(OS), Linux)
|
||||
BUNDLENAME = $(APP)-$(OS)-$(DISPDRIVER)-$(ARCH)-$(VERSION)
|
||||
endif
|
||||
|
||||
CORES = ppsspp dolphin atari800 bluemsx citra swanstation fbneo fceumm flycast gambatte gearsystem genesis_plus_gx handy lutro mednafen_ngp mednafen_pce mednafen_pce_fast mednafen_pcfx mednafen_psx mednafen_saturn mednafen_supergrafx mednafen_vb mednafen_wswan mgba melonds mupen64plus_next np2kai o2em pcsx2 pcsx_rearmed picodrive pokemini prosystem snes9x stella2014 vecx virtualjaguar
|
||||
CORES = ppsspp dolphin atari800 bluemsx swanstation fbneo fceumm gambatte gearsystem genesis_plus_gx handy lutro mednafen_ngp mednafen_pce mednafen_pce_fast mednafen_pcfx mednafen_psx mednafen_saturn mednafen_supergrafx mednafen_vb mednafen_wswan mgba melonds mupen64plus_next np2kai o2em pcsx_rearmed picodrive pokemini prosystem snes9x stella2014 vecx virtualjaguar
|
||||
|
||||
ifeq ($(ARCH), arm)
|
||||
CORES := $(filter-out citra dolphin flycast mednafen_pcfx mednafen_saturn melonds mupen64plus_next pcsx2 ppsspp swanstation,$(CORES))
|
||||
CORES := $(filter-out swanstation,$(CORES))
|
||||
CORES := $(filter-out mupen64plus_next,$(CORES))
|
||||
CORES := $(filter-out mednafen_pcfx,$(CORES))
|
||||
CORES := $(filter-out mednafen_saturn,$(CORES))
|
||||
CORES := $(filter-out melonds,$(CORES))
|
||||
endif
|
||||
ifeq ($(ARCH), arm64)
|
||||
CORES := $(filter-out pcsx2,$(CORES))
|
||||
ifeq ($(OS), Linux)
|
||||
CORES := $(filter-out mupen64plus_next,$(CORES))
|
||||
endif
|
||||
endif
|
||||
ifeq ($(OS), OSX)
|
||||
CORES := $(filter-out pcsx2,$(CORES))
|
||||
ifeq ($(ARCH), x86_64)
|
||||
CORES := $(filter-out citra,$(CORES))
|
||||
endif
|
||||
CORES := $(filter-out mednafen_saturn,$(CORES))
|
||||
CORES := $(filter-out mupen64plus_next,$(CORES))
|
||||
CORES := $(filter-out picodrive,$(CORES))
|
||||
endif
|
||||
|
||||
DYLIBS = $(addprefix cores/, $(addsuffix _libretro.dylib,$(CORES)))
|
||||
|
||||
@@ -4,7 +4,7 @@ Ludo is a work in progress libretro frontend written in go.
|
||||
|
||||
<img src="https://raw.githubusercontent.com/kivutar/ludo-assets/master/illustration.png" />
|
||||
|
||||
It supports software-rendered and desktop OpenGL libretro cores. Hardware-rendered cores may require a newer OpenGL version than the frontend itself.
|
||||
It is able to launch most non GL libretro cores.
|
||||
|
||||
It works on OSX, Linux, Linux ARM and Windows. You can download releases [here](https://github.com/libretro/ludo/releases)
|
||||
|
||||
|
||||
+5
-37
@@ -1,10 +1,10 @@
|
||||
package core
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
@@ -15,34 +15,6 @@ import (
|
||||
"github.com/libretro/ludo/video"
|
||||
)
|
||||
|
||||
var mainThread = make(chan func())
|
||||
|
||||
func init() {
|
||||
// GLFW initialization and input setup must run on the main OS thread.
|
||||
runtime.LockOSThread()
|
||||
}
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
var code int
|
||||
go func() {
|
||||
code = m.Run()
|
||||
close(mainThread)
|
||||
}()
|
||||
for f := range mainThread {
|
||||
f()
|
||||
}
|
||||
os.Exit(code)
|
||||
}
|
||||
|
||||
func onMainThread(f func()) {
|
||||
done := make(chan struct{})
|
||||
mainThread <- func() {
|
||||
f()
|
||||
close(done)
|
||||
}
|
||||
<-done
|
||||
}
|
||||
|
||||
type WindowMock struct{}
|
||||
|
||||
func (m WindowMock) GetFramebufferSize() (width, height int) { return 320, 240 }
|
||||
@@ -229,21 +201,17 @@ func Test_coreLoadGame(t *testing.T) {
|
||||
|
||||
Init(&video.Video{})
|
||||
|
||||
var initErr error
|
||||
onMainThread(func() { initErr = glfw.Init() })
|
||||
if initErr != nil {
|
||||
t.Fatalf("glfw.Init() error = %v", initErr)
|
||||
if err := glfw.Init(); err != nil {
|
||||
log.Fatalln("failed to initialize glfw")
|
||||
}
|
||||
t.Cleanup(func() { onMainThread(glfw.Terminate) })
|
||||
t.Cleanup(glfw.Terminate)
|
||||
|
||||
if err := Load("testdata/vecx_libretro" + ext); err != nil {
|
||||
t.Fatalf("Load() error = %v", err)
|
||||
}
|
||||
|
||||
var loadGameErr error
|
||||
got := utils.CaptureOutput(func() {
|
||||
onMainThread(func() { loadGameErr = LoadGame("testdata/Polar Rescue (USA).vec") })
|
||||
})
|
||||
got := utils.CaptureOutput(func() { loadGameErr = LoadGame("testdata/Polar Rescue (USA).vec") })
|
||||
if loadGameErr != nil {
|
||||
t.Fatalf("LoadGame() error = %v", loadGameErr)
|
||||
}
|
||||
|
||||
+3
-8
@@ -151,18 +151,13 @@ func environment(cmd uint32, data unsafe.Pointer) bool {
|
||||
if (hw.HWContextType != libretro.HWContextOpenGL && hw.HWContextType != libretro.HWContextOpenGLCore) || hw.ContextReset == nil {
|
||||
return false
|
||||
}
|
||||
state.Core.HWRenderCallback = hw
|
||||
vid.SetHWRenderContext(hw)
|
||||
log.Println("[Env]: HWContextType:", hw.HWContextType)
|
||||
if vid != nil && vid.Window != nil && !state.CoreRunning {
|
||||
if err := vid.ReconfigureHWContext(settings.Current.VideoFullscreen); err != nil {
|
||||
vid.SetHWRenderContext(state.Core.HWRenderCallback)
|
||||
log.Printf("[Env]: Could not create HW context %d (%d.%d): %v",
|
||||
hw.HWContextType, hw.VersionMajor, hw.VersionMinor, err)
|
||||
return false
|
||||
}
|
||||
vid.ReconfigureHWContext(settings.Current.VideoFullscreen)
|
||||
state.MenuContextResetNeeded = true
|
||||
}
|
||||
state.Core.HWRenderCallback = hw
|
||||
log.Println("[Env]: HWContextType:", hw.HWContextType)
|
||||
return true
|
||||
case libretro.EnvironmentSetHWSharedContext:
|
||||
// All HW cores already get an isolated context shared with the frontend.
|
||||
|
||||
@@ -14,7 +14,7 @@ require (
|
||||
github.com/pelletier/go-toml v1.9.5
|
||||
github.com/tanema/gween v0.0.0-20250522035225-e874ee3ae01a
|
||||
github.com/youpy/go-wav v0.3.2
|
||||
golang.org/x/image v0.41.0
|
||||
golang.org/x/image v0.39.0
|
||||
golang.org/x/mobile v0.0.0-20260410095206-2cfb76559b7b
|
||||
)
|
||||
|
||||
@@ -26,7 +26,7 @@ require (
|
||||
github.com/bodgit/windows v1.0.1 // indirect
|
||||
github.com/dsnet/compress v0.0.2-0.20230904184137-39efe44ab707 // indirect
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
|
||||
github.com/klauspost/compress v1.18.7 // indirect
|
||||
github.com/klauspost/compress v1.18.5 // indirect
|
||||
github.com/klauspost/pgzip v1.2.6 // indirect
|
||||
github.com/mikelolasagasti/xz v1.0.1 // indirect
|
||||
github.com/minio/minlz v1.0.1 // indirect
|
||||
@@ -39,7 +39,7 @@ require (
|
||||
github.com/zaf/g711 v1.4.0 // indirect
|
||||
go4.org v0.0.0-20230225012048-214862532bf5 // indirect
|
||||
golang.org/x/sys v0.43.0 // indirect
|
||||
golang.org/x/text v0.37.0 // indirect
|
||||
golang.org/x/text v0.36.0 // indirect
|
||||
)
|
||||
|
||||
go 1.25.0
|
||||
|
||||
@@ -96,8 +96,8 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o
|
||||
github.com/kivutar/glfw/v3.4/glfw v0.0.0-20260410202638-4349e4d5a0ee h1:PuDX5smvEEVGSd2s66iGqGtmrSldeBusjnLgIi92Sl4=
|
||||
github.com/kivutar/glfw/v3.4/glfw v0.0.0-20260410202638-4349e4d5a0ee/go.mod h1:T5Dn0JwIJOX1euPZ/iT4tq6nFYtmukjcYa7937HuYK8=
|
||||
github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
|
||||
github.com/klauspost/compress v1.18.7 h1:aUyZsS4kH3QTKurYhAOwAHxllVPnOthb3vPfnF1Ehjw=
|
||||
github.com/klauspost/compress v1.18.7/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
|
||||
github.com/klauspost/compress v1.18.5 h1:/h1gH5Ce+VWNLSWqPzOVn6XBO+vJbCNGvjoaGBFW2IE=
|
||||
github.com/klauspost/compress v1.18.5/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
|
||||
github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
|
||||
github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU=
|
||||
github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
|
||||
@@ -180,8 +180,8 @@ golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMx
|
||||
golang.org/x/image v0.0.0-20190321063152-3fc05d484e9f/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/image v0.41.0 h1:8wS72eGJMJaBxK6okTzd4WaXumUlTVlb753MlsSvTCo=
|
||||
golang.org/x/image v0.41.0/go.mod h1:uIc348UZMSvS5Z65CVZ7iDPaNobNFEPeJ4kbqTOszmA=
|
||||
golang.org/x/image v0.39.0 h1:skVYidAEVKgn8lZ602XO75asgXBgLj9G/FE3RbuPFww=
|
||||
golang.org/x/image v0.39.0/go.mod h1:sIbmppfU+xFLPIG0FoVUTvyBMmgng1/XAMhQ2ft0hpA=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
@@ -259,8 +259,8 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc=
|
||||
golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38=
|
||||
golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg=
|
||||
golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
|
||||
+21
-13
@@ -467,19 +467,20 @@ type (
|
||||
)
|
||||
|
||||
var (
|
||||
environment environmentFunc
|
||||
videoRefresh videoRefreshFunc
|
||||
audioSample audioSampleFunc
|
||||
audioSampleBatch audioSampleBatchFunc
|
||||
inputPoll inputPollFunc
|
||||
inputState inputStateFunc
|
||||
log logFunc
|
||||
getTimeUsec getTimeUsecFunc
|
||||
getProcAddress getProcAddressFunc
|
||||
releaseContext func()
|
||||
acquireContext func()
|
||||
coreCalls chan func()
|
||||
frontendCalls chan func()
|
||||
environment environmentFunc
|
||||
videoRefresh videoRefreshFunc
|
||||
audioSample audioSampleFunc
|
||||
audioSampleBatch audioSampleBatchFunc
|
||||
inputPoll inputPollFunc
|
||||
inputState inputStateFunc
|
||||
log logFunc
|
||||
getTimeUsec getTimeUsecFunc
|
||||
getCurrentFramebuffer getCurrentFramebufferFunc
|
||||
getProcAddress getProcAddressFunc
|
||||
releaseContext func()
|
||||
acquireContext func()
|
||||
coreCalls chan func()
|
||||
frontendCalls chan func()
|
||||
)
|
||||
|
||||
// SetContextCallbacks hands the current graphics context between the calling
|
||||
@@ -622,6 +623,7 @@ func (core *Core) Deinit() {
|
||||
inputState = nil
|
||||
log = nil
|
||||
getTimeUsec = nil
|
||||
getCurrentFramebuffer = nil
|
||||
getProcAddress = nil
|
||||
}
|
||||
|
||||
@@ -872,6 +874,11 @@ func coreGetTimeUsec() C.uint64_t {
|
||||
return C.uint64_t(getTimeUsec())
|
||||
}
|
||||
|
||||
//export coreGetCurrentFramebuffer
|
||||
func coreGetCurrentFramebuffer() C.uintptr_t {
|
||||
return C.uintptr_t(getCurrentFramebuffer())
|
||||
}
|
||||
|
||||
//export coreGetProcAddress
|
||||
func coreGetProcAddress(sym *C.char) C.uintptr_t {
|
||||
return C.uintptr_t(getProcAddress(C.GoString(sym)))
|
||||
@@ -1054,6 +1061,7 @@ func SetHWRenderCallback(
|
||||
hwrc := HWRenderCallback{}
|
||||
hwrc.HWContextType = uint32(c.context_type)
|
||||
|
||||
getCurrentFramebuffer = currentFramebuffer
|
||||
getProcAddress = procAddress
|
||||
SetCurrentFramebufferValue(currentFramebuffer())
|
||||
c.get_current_framebuffer = (C.retro_hw_get_current_framebuffer_t)(C.coreGetCurrentFramebuffer_cgo)
|
||||
|
||||
@@ -28,8 +28,8 @@ func DlOpen(path string) (DlHandle, error) {
|
||||
defer C.free(unsafe.Pointer(cpath))
|
||||
|
||||
h := C.dlopen(cpath, C.RTLD_LAZY|C.RTLD_GLOBAL)
|
||||
if h == nil {
|
||||
cerr := C.dlerror()
|
||||
cerr := C.dlerror()
|
||||
if h == nil || cerr != nil {
|
||||
err := C.GoString(cerr)
|
||||
return nil, errors.New(err)
|
||||
}
|
||||
|
||||
@@ -52,20 +52,16 @@ func runLoop(vid *video.Video, m *menu.Menu) {
|
||||
for !vid.Window.ShouldClose() {
|
||||
currTime = time.Now()
|
||||
dt := float32(currTime.Sub(prevTime)) / 1000000000
|
||||
if state.MenuContextResetNeeded {
|
||||
m.ContextReset()
|
||||
state.MenuContextResetNeeded = false
|
||||
}
|
||||
glfw.PollEvents()
|
||||
m.ProcessHotkeys()
|
||||
ntf.Process(dt)
|
||||
m.UpdatePalette()
|
||||
input.Poll()
|
||||
menuActive := state.MenuActive
|
||||
if menuActive {
|
||||
m.Update(dt)
|
||||
}
|
||||
if state.MenuContextResetNeeded {
|
||||
m.ContextReset()
|
||||
state.MenuContextResetNeeded = false
|
||||
}
|
||||
if !menuActive {
|
||||
if !state.MenuActive {
|
||||
if state.CoreRunning {
|
||||
vid.BeginCore()
|
||||
state.Core.Run()
|
||||
@@ -86,6 +82,7 @@ func runLoop(vid *video.Video, m *menu.Menu) {
|
||||
savefiles.SaveSRAM()
|
||||
}
|
||||
} else {
|
||||
m.Update(dt)
|
||||
vid.BeginFrontendFrame()
|
||||
vid.RenderPresentedFrame()
|
||||
fulfillPendingScreenshot(vid)
|
||||
|
||||
+1
-1
@@ -196,7 +196,7 @@ func (m *Menu) ProcessHotkeys() {
|
||||
// Toggle fullscreen if ActionFullscreenToggle is released
|
||||
if input.Released[0][input.ActionFullscreenToggle] == 1 {
|
||||
settings.Current.VideoFullscreen = !settings.Current.VideoFullscreen
|
||||
m.SetFullscreen(settings.Current.VideoFullscreen)
|
||||
m.ReconfigureVideo(settings.Current.VideoFullscreen)
|
||||
err := settings.Save()
|
||||
if err != nil {
|
||||
ntf.DisplayAndLogf(ntf.Error, "Menu", "Error saving settings: %v", err)
|
||||
|
||||
+18
-5
@@ -6,6 +6,8 @@ package menu
|
||||
import (
|
||||
"path/filepath"
|
||||
|
||||
"github.com/libretro/ludo/core"
|
||||
ntf "github.com/libretro/ludo/notifications"
|
||||
"github.com/libretro/ludo/settings"
|
||||
"github.com/libretro/ludo/state"
|
||||
"github.com/libretro/ludo/utils"
|
||||
@@ -102,13 +104,24 @@ func (m *Menu) ContextReset() {
|
||||
m.icons[filename] = video.NewImage(path)
|
||||
}
|
||||
|
||||
// Every retained scene's texture names belong to the old context.
|
||||
for _, scene := range m.stack {
|
||||
list := scene.Entry()
|
||||
for i := range list.children {
|
||||
list.children[i].thumbnail = 0
|
||||
currentScreenIndex := len(m.stack) - 1
|
||||
curList := m.stack[currentScreenIndex].Entry()
|
||||
for i := range curList.children {
|
||||
curList.children[i].thumbnail = 0
|
||||
}
|
||||
}
|
||||
|
||||
// ReconfigureVideo recreates the window and restores any running HW-render core state.
|
||||
func (m *Menu) ReconfigureVideo(fullscreen bool) {
|
||||
m.Reconfigure(fullscreen)
|
||||
if state.CoreRunning && state.Core.HWRenderCallback != nil {
|
||||
if err := m.ResetHWContext(); err != nil {
|
||||
core.Unload()
|
||||
state.MenuActive = true
|
||||
ntf.DisplayAndLog(ntf.Error, "Video", err.Error())
|
||||
}
|
||||
}
|
||||
m.ContextReset()
|
||||
}
|
||||
|
||||
// WarpToQuickMenu loads the contextual menu for games that are launched from
|
||||
|
||||
@@ -11,7 +11,6 @@ var severityBgColor map[ntf.Severity]video.Color
|
||||
// RenderNotifications draws the list of notification messages on the viewport
|
||||
func (m *Menu) RenderNotifications() {
|
||||
fbw, fbh := m.GetFramebufferSize()
|
||||
m.ratio = float32(fbw) / 1920
|
||||
m.Font.UpdateResolution(fbw, fbh)
|
||||
var h float32 = 75
|
||||
stack := h
|
||||
|
||||
@@ -16,7 +16,7 @@ func buildCoreOptions() Scene {
|
||||
var list sceneCoreOptions
|
||||
list.label = "Core Options"
|
||||
|
||||
if core.Options == nil || len(core.Options.Vars) == 0 {
|
||||
if core.Options == nil {
|
||||
list.children = append(list.children, entry{
|
||||
label: "No options",
|
||||
icon: "subsetting",
|
||||
|
||||
@@ -30,6 +30,7 @@ func buildSavestates() Scene {
|
||||
state.PendingScreenshotDone = func(err error) {
|
||||
if err != nil {
|
||||
ntf.DisplayAndLog(ntf.Error, "Menu", err.Error())
|
||||
return
|
||||
}
|
||||
err = savestates.Save(name)
|
||||
if err != nil {
|
||||
|
||||
@@ -164,7 +164,7 @@ var incrCallbacks = map[string]callbackIncrement{
|
||||
v := f.Value().(bool)
|
||||
v = !v
|
||||
f.Set(v)
|
||||
menu.SetFullscreen(settings.Current.VideoFullscreen)
|
||||
menu.ReconfigureVideo(settings.Current.VideoFullscreen)
|
||||
settings.Save()
|
||||
},
|
||||
"VideoMonitorIndex": func(f *structs.Field, direction int) {
|
||||
@@ -177,7 +177,7 @@ var incrCallbacks = map[string]callbackIncrement{
|
||||
v = len(glfw.GetMonitors()) - 1
|
||||
}
|
||||
f.Set(v)
|
||||
menu.SetFullscreen(settings.Current.VideoFullscreen)
|
||||
menu.ReconfigureVideo(settings.Current.VideoFullscreen)
|
||||
settings.Save()
|
||||
},
|
||||
"VideoFilter": func(f *structs.Field, direction int) {
|
||||
|
||||
+3
-7
@@ -50,17 +50,13 @@ func New(vars []VariableInterface) (*Options, error) {
|
||||
|
||||
// Cache core options
|
||||
for _, v := range vars {
|
||||
choices := v.Choices()
|
||||
// A core may expose an empty selector, e.g. when no BIOS files are found.
|
||||
if len(choices) == 0 {
|
||||
continue
|
||||
}
|
||||
v := v
|
||||
o.Vars = append(o.Vars, &Variable{
|
||||
Key: v.Key(),
|
||||
Desc: v.Desc(),
|
||||
Choices: choices,
|
||||
Choices: v.Choices(),
|
||||
Default: v.DefaultValue(),
|
||||
Choice: utils.IndexOfString(v.DefaultValue(), choices),
|
||||
Choice: utils.IndexOfString(v.DefaultValue(), v.Choices()),
|
||||
})
|
||||
}
|
||||
o.Updated = true
|
||||
|
||||
+10
-1
@@ -33,6 +33,7 @@ func ipsAllocTargetData(patch, source []byte) ([]byte, error) {
|
||||
size |= int(patch[offset]) << 8
|
||||
offset++
|
||||
size |= int(patch[offset]) << 0
|
||||
offset++
|
||||
targetLength = size
|
||||
prov := make([]byte, targetLength)
|
||||
return prov, nil
|
||||
@@ -116,7 +117,15 @@ func applyIPS(patch, source []byte) (*[]byte, error) {
|
||||
offset++
|
||||
|
||||
if address == EOF {
|
||||
if offset == len(patch) || offset == len(patch)-3 {
|
||||
if offset == len(patch) {
|
||||
return &targetData, nil
|
||||
} else if offset == len(patch)-3 {
|
||||
size := int(patch[offset]) << 16
|
||||
offset++
|
||||
size |= int(patch[offset]) << 8
|
||||
offset++
|
||||
size |= int(patch[offset]) << 0
|
||||
offset++
|
||||
return &targetData, nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,17 +43,13 @@ func defaultSettings() Settings {
|
||||
"Nintendo - Game Boy Advance": "mgba_libretro",
|
||||
"Nintendo - Game Boy Color": "gambatte_libretro",
|
||||
"Nintendo - Game Boy": "gambatte_libretro",
|
||||
"Nintendo - GameCube": "dolphin_libretro",
|
||||
"Nintendo - Nintendo 3DS": "citra_libretro",
|
||||
"Nintendo - Nintendo 64": "mupen64plus_next_libretro",
|
||||
"Nintendo - Nintendo Entertainment System": "fceumm_libretro",
|
||||
"Nintendo - Nintendo DS": "melonds_libretro",
|
||||
"Nintendo - Pokemon Mini": "pokemini_libretro",
|
||||
"Nintendo - Super Nintendo Entertainment System": "snes9x_libretro",
|
||||
"Nintendo - Virtual Boy": "mednafen_vb_libretro",
|
||||
"Nintendo - Wii": "dolphin_libretro",
|
||||
"Sega - 32X": "picodrive_libretro",
|
||||
"Sega - Dreamcast": "flycast_libretro",
|
||||
"Sega - Game Gear": "genesis_plus_gx_libretro",
|
||||
"Sega - Master System - Mark III": "genesis_plus_gx_libretro",
|
||||
"Sega - Mega Drive - Genesis": "genesis_plus_gx_libretro",
|
||||
@@ -64,8 +60,6 @@ func defaultSettings() Settings {
|
||||
"SNK - Neo Geo Pocket Color": "mednafen_ngp_libretro",
|
||||
"SNK - Neo Geo Pocket": "mednafen_ngp_libretro",
|
||||
"Sony - PlayStation": playstationCore,
|
||||
"Sony - PlayStation 2": "pcsx2_libretro",
|
||||
"Sony - PlayStation Portable": "ppsspp_libretro",
|
||||
},
|
||||
FileDirectory: usr.HomeDir,
|
||||
CoresDirectory: "./cores",
|
||||
|
||||
@@ -32,7 +32,7 @@ func TestHardwareContextIsolation(t *testing.T) {
|
||||
}
|
||||
defer w.Destroy()
|
||||
w.MakeContextCurrent()
|
||||
if err := initGL(); err != nil {
|
||||
if err := gl.Init(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
t.Log("GL:", gl.GoStr(gl.GetString(gl.VERSION)))
|
||||
@@ -138,8 +138,6 @@ func TestHardwareContextIsolation(t *testing.T) {
|
||||
hw.BottomLeftOrigin = bottomLeft
|
||||
v.BeginFrontendFrame()
|
||||
fbw, fbh := w.GetFramebufferSize()
|
||||
v.Geom.AspectRatio = float64(fbw) / float64(fbh)
|
||||
v.presentW, v.presentH = int32(fbw), int32(fbh)
|
||||
gl.ClearColor(1, 0, 0, 1)
|
||||
gl.Clear(gl.COLOR_BUFFER_BIT)
|
||||
gl.Enable(gl.SCISSOR_TEST)
|
||||
|
||||
@@ -4,9 +4,14 @@ var vertexShader = `
|
||||
#if __VERSION__ >= 130
|
||||
#define COMPAT_VARYING out
|
||||
#define COMPAT_ATTRIBUTE in
|
||||
#define COMPAT_TEXTURE texture
|
||||
#define COMPAT_FRAGCOLOR FragColor
|
||||
out vec4 COMPAT_FRAGCOLOR;
|
||||
#else
|
||||
#define COMPAT_VARYING varying
|
||||
#define COMPAT_ATTRIBUTE attribute
|
||||
#define COMPAT_TEXTURE texture2D
|
||||
#define COMPAT_FRAGCOLOR gl_FragColor
|
||||
#endif
|
||||
|
||||
COMPAT_ATTRIBUTE vec2 vert;
|
||||
|
||||
-63
@@ -1,63 +0,0 @@
|
||||
package video
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/go-gl/gl/v2.1/gl"
|
||||
"github.com/go-gl/glfw/v3.4/glfw"
|
||||
)
|
||||
|
||||
var (
|
||||
glslVersion = "#version 120\n"
|
||||
genVertexArrays = gl.GenVertexArrays
|
||||
bindVertexArray = gl.BindVertexArray
|
||||
genFramebuffers = gl.GenFramebuffers
|
||||
bindFramebuffer = gl.BindFramebuffer
|
||||
deleteFramebuffers = gl.DeleteFramebuffers
|
||||
framebufferTexture2D = gl.FramebufferTexture2D
|
||||
checkFramebufferStatus = gl.CheckFramebufferStatus
|
||||
genRenderbuffers = gl.GenRenderbuffers
|
||||
bindRenderbuffer = gl.BindRenderbuffer
|
||||
deleteRenderbuffers = gl.DeleteRenderbuffers
|
||||
renderbufferStorage = gl.RenderbufferStorage
|
||||
framebufferRenderbuffer = gl.FramebufferRenderbuffer
|
||||
)
|
||||
|
||||
// The frontend and hidden core context use the same hints. Select their API
|
||||
// from the actual context capabilities, including macOS's legacy extensions.
|
||||
func initGL() error {
|
||||
if err := gl.Init(); err != nil {
|
||||
return err
|
||||
}
|
||||
window := glfw.GetCurrentContext()
|
||||
major, minor := window.GetAttrib(glfw.ContextVersionMajor), window.GetAttrib(glfw.ContextVersionMinor)
|
||||
glslVersion = "#version 120\n"
|
||||
if major > 3 || (major == 3 && minor >= 2) {
|
||||
glslVersion = "#version 150\n"
|
||||
} else if major == 3 {
|
||||
glslVersion = "#version 130\n"
|
||||
}
|
||||
|
||||
genVertexArrays, bindVertexArray = gl.GenVertexArrays, gl.BindVertexArray
|
||||
genFramebuffers, bindFramebuffer, deleteFramebuffers = gl.GenFramebuffers, gl.BindFramebuffer, gl.DeleteFramebuffers
|
||||
framebufferTexture2D, checkFramebufferStatus = gl.FramebufferTexture2D, gl.CheckFramebufferStatus
|
||||
genRenderbuffers, bindRenderbuffer, deleteRenderbuffers = gl.GenRenderbuffers, gl.BindRenderbuffer, gl.DeleteRenderbuffers
|
||||
renderbufferStorage, framebufferRenderbuffer = gl.RenderbufferStorage, gl.FramebufferRenderbuffer
|
||||
|
||||
if major < 3 && !glfw.ExtensionSupported("GL_ARB_vertex_array_object") {
|
||||
if !glfw.ExtensionSupported("GL_APPLE_vertex_array_object") {
|
||||
return errors.New("OpenGL vertex array objects are not supported")
|
||||
}
|
||||
genVertexArrays, bindVertexArray = gl.GenVertexArraysAPPLE, gl.BindVertexArrayAPPLE
|
||||
}
|
||||
if major < 3 && !glfw.ExtensionSupported("GL_ARB_framebuffer_object") {
|
||||
if !glfw.ExtensionSupported("GL_EXT_framebuffer_object") {
|
||||
return errors.New("OpenGL framebuffer objects are not supported")
|
||||
}
|
||||
genFramebuffers, bindFramebuffer, deleteFramebuffers = gl.GenFramebuffersEXT, gl.BindFramebufferEXT, gl.DeleteFramebuffersEXT
|
||||
framebufferTexture2D, checkFramebufferStatus = gl.FramebufferTexture2DEXT, gl.CheckFramebufferStatusEXT
|
||||
genRenderbuffers, bindRenderbuffer, deleteRenderbuffers = gl.GenRenderbuffersEXT, gl.BindRenderbufferEXT, gl.DeleteRenderbuffersEXT
|
||||
renderbufferStorage, framebufferRenderbuffer = gl.RenderbufferStorageEXT, gl.FramebufferRenderbufferEXT
|
||||
}
|
||||
return nil
|
||||
}
|
||||
+38
-10
@@ -1,7 +1,6 @@
|
||||
package video
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"image"
|
||||
"image/png"
|
||||
"os"
|
||||
@@ -11,21 +10,42 @@ import (
|
||||
"github.com/go-gl/gl/v2.1/gl"
|
||||
|
||||
"github.com/libretro/ludo/settings"
|
||||
"github.com/libretro/ludo/state"
|
||||
)
|
||||
|
||||
func (video *Video) captureBackbufferScreenshot(name string) error {
|
||||
outW, outH := video.Geom.BaseWidth, video.Geom.BaseHeight
|
||||
if outW < 1 || outH < 1 {
|
||||
return errors.New("invalid screenshot output size")
|
||||
func (video *Video) screenshotOutputSize() (int32, int32) {
|
||||
width := int32(video.Geom.BaseWidth)
|
||||
height := int32(video.Geom.BaseHeight)
|
||||
if width < 1 {
|
||||
width = video.width
|
||||
}
|
||||
if height < 1 {
|
||||
height = video.height
|
||||
}
|
||||
if width < 1 {
|
||||
width = video.presentW
|
||||
}
|
||||
if height < 1 {
|
||||
height = video.presentH
|
||||
}
|
||||
if width < 1 {
|
||||
width = 1
|
||||
}
|
||||
if height < 1 {
|
||||
height = 1
|
||||
}
|
||||
return width, height
|
||||
}
|
||||
|
||||
func (video *Video) captureBackbufferScreenshot(name string) error {
|
||||
outW, outH := video.screenshotOutputSize()
|
||||
|
||||
x := video.presentX
|
||||
y := video.presentY
|
||||
w := video.presentW
|
||||
h := video.presentH
|
||||
if !state.CoreRunning || video.width < 1 || video.height < 1 || w < 1 || h < 1 {
|
||||
return errors.New("no game frame to capture")
|
||||
if w < 1 || h < 1 {
|
||||
fbw, fbh := video.Window.GetFramebufferSize()
|
||||
x, y, w, h = int32(0), int32(0), int32(fbw), int32(fbh)
|
||||
}
|
||||
|
||||
var oldReadBuffer int32
|
||||
@@ -38,8 +58,8 @@ func (video *Video) captureBackbufferScreenshot(name string) error {
|
||||
gl.ReadBuffer(uint32(oldReadBuffer))
|
||||
|
||||
flipped := imaging.FlipV(img)
|
||||
if flipped.Bounds().Dx() != outW || flipped.Bounds().Dy() != outH {
|
||||
flipped = imaging.Resize(flipped, outW, outH, imaging.Linear)
|
||||
if int32(flipped.Bounds().Dx()) != outW || int32(flipped.Bounds().Dy()) != outH {
|
||||
flipped = imaging.Resize(flipped, int(outW), int(outH), imaging.Linear)
|
||||
}
|
||||
|
||||
err := os.MkdirAll(settings.Current.ScreenshotsDirectory, os.ModePerm)
|
||||
@@ -57,6 +77,14 @@ func (video *Video) captureBackbufferScreenshot(name string) error {
|
||||
return png.Encode(fd, flipped)
|
||||
}
|
||||
|
||||
// TakeScreenshot captures a screenshot without advancing the core.
|
||||
func (video *Video) TakeScreenshot(name string) error {
|
||||
video.BeginFrontendFrame()
|
||||
|
||||
video.Render()
|
||||
return video.captureBackbufferScreenshot(name)
|
||||
}
|
||||
|
||||
// TakeScreenshotInFrontendFrame captures a screenshot while the caller already
|
||||
// owns a frontend frame and has already rendered the current game frame.
|
||||
func (video *Video) TakeScreenshotInFrontendFrame(name string) error {
|
||||
|
||||
@@ -23,9 +23,6 @@ func newProgram(vertexShaderSource, fragmentShaderSource string) (uint32, error)
|
||||
|
||||
gl.AttachShader(program, vertexShader)
|
||||
gl.AttachShader(program, fragmentShader)
|
||||
// Game and menu programs share a VAO, so their attribute locations must match.
|
||||
gl.BindAttribLocation(program, 0, gl.Str("vert\x00"))
|
||||
gl.BindAttribLocation(program, 1, gl.Str("vertTexCoord\x00"))
|
||||
gl.LinkProgram(program)
|
||||
|
||||
var status int32
|
||||
@@ -60,7 +57,7 @@ func newProgram(vertexShaderSource, fragmentShaderSource string) (uint32, error)
|
||||
func compileShader(source string, shaderType uint32) (uint32, error) {
|
||||
shader := gl.CreateShader(shaderType)
|
||||
|
||||
csources, free := gl.Strs(glslVersion + source)
|
||||
csources, free := gl.Strs(source)
|
||||
gl.ShaderSource(shader, 1, csources, nil)
|
||||
free()
|
||||
gl.CompileShader(shader)
|
||||
|
||||
@@ -29,7 +29,7 @@ func TestSoftwareFrameUploadBounds(t *testing.T) {
|
||||
}
|
||||
defer w.Destroy()
|
||||
w.MakeContextCurrent()
|
||||
if err := initGL(); err != nil {
|
||||
if err := gl.Init(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
|
||||
+55
-81
@@ -4,7 +4,6 @@
|
||||
package video
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"log"
|
||||
"path/filepath"
|
||||
"unsafe"
|
||||
@@ -27,9 +26,6 @@ type Video struct {
|
||||
FontLg *Font
|
||||
title string
|
||||
|
||||
windowedX, windowedY int
|
||||
windowedWidth, windowedHeight int
|
||||
|
||||
program uint32 // current program used for the game quad
|
||||
defaultProgram uint32 // default program used for the game quad
|
||||
sharpBilinearProgram uint32 // sharp bilinear program used for the game quad
|
||||
@@ -82,29 +78,19 @@ func Init(fullscreen bool) *Video {
|
||||
return vid
|
||||
}
|
||||
|
||||
// SetFullscreen changes the window mode or monitor without recreating GL contexts.
|
||||
func (video *Video) SetFullscreen(fullscreen bool) {
|
||||
if fullscreen {
|
||||
if video.Window.GetMonitor() == nil {
|
||||
video.windowedWidth, video.windowedHeight = video.Window.GetSize()
|
||||
// Wayland does not expose window positions.
|
||||
if glfw.GetPlatform() != glfw.PlatformWayland {
|
||||
video.windowedX, video.windowedY = video.Window.GetPos()
|
||||
}
|
||||
}
|
||||
monitor := glfw.GetMonitors()[settings.Current.VideoMonitorIndex]
|
||||
modes := monitor.GetVideoModes()
|
||||
mode := modes[len(modes)-1]
|
||||
video.Window.SetMonitor(monitor, 0, 0, mode.Width, mode.Height, mode.RefreshRate)
|
||||
} else if video.Window.GetMonitor() != nil {
|
||||
video.Window.SetMonitor(nil, video.windowedX, video.windowedY,
|
||||
video.windowedWidth, video.windowedHeight, glfw.DontCare)
|
||||
// Reconfigure destroys and recreates the window with new attributes
|
||||
func (video *Video) Reconfigure(fullscreen bool) {
|
||||
if video.Window != nil {
|
||||
video.RunContextDestroy()
|
||||
video.DestroySharedHWContext()
|
||||
video.Window.Destroy()
|
||||
}
|
||||
video.Configure(fullscreen)
|
||||
}
|
||||
|
||||
// ReconfigureHWContext recreates the visible window using the currently stored
|
||||
// HW context hints while preserving the current window size when windowed.
|
||||
func (video *Video) ReconfigureHWContext(fullscreen bool) error {
|
||||
func (video *Video) ReconfigureHWContext(fullscreen bool) {
|
||||
width := 384 * 2
|
||||
height := 240 * 2
|
||||
if video.Window != nil && !fullscreen {
|
||||
@@ -116,7 +102,12 @@ func (video *Video) ReconfigureHWContext(fullscreen bool) error {
|
||||
height = 240 * 2
|
||||
}
|
||||
}
|
||||
return video.configureWithSize(width, height, fullscreen)
|
||||
if video.Window != nil {
|
||||
video.RunContextDestroy()
|
||||
video.DestroySharedHWContext()
|
||||
video.Window.Destroy()
|
||||
}
|
||||
video.configureWithSize(width, height, fullscreen)
|
||||
}
|
||||
|
||||
// CreateSharedHWContext creates a hidden context shared with the visible frontend context.
|
||||
@@ -374,33 +365,10 @@ func (video *Video) resetGameTexture(width, height int32) {
|
||||
|
||||
// Configure instanciates the video package
|
||||
func (video *Video) Configure(fullscreen bool) {
|
||||
if err := video.configureWithSize(384*2, 240*2, fullscreen); err != nil {
|
||||
panic("Window creation failed:" + err.Error())
|
||||
}
|
||||
video.configureWithSize(384*2, 240*2, fullscreen)
|
||||
}
|
||||
|
||||
func (video *Video) configureWithSize(width, height int, fullscreen bool) error {
|
||||
if fullscreen && video.windowedWidth != 0 {
|
||||
width, height = video.windowedWidth, video.windowedHeight
|
||||
}
|
||||
video.configureWindowHints()
|
||||
window, err := glfw.CreateWindow(width, height, video.title, nil, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// GLFW may report a platform error without returning it through the Go binding.
|
||||
if window == nil {
|
||||
return errors.New("window creation failed")
|
||||
}
|
||||
|
||||
// Keep the old context and its resources until the replacement exists.
|
||||
if video.Window != nil {
|
||||
video.RunContextDestroy()
|
||||
video.DestroySharedHWContext()
|
||||
video.Window.Destroy()
|
||||
}
|
||||
video.Window = window
|
||||
|
||||
func (video *Video) configureWithSize(width, height int, fullscreen bool) {
|
||||
// Object names from the old share group must not be reused in this context.
|
||||
video.texID = 0
|
||||
video.cachedFrameTexID = 0
|
||||
@@ -409,15 +377,33 @@ func (video *Video) configureWithSize(width, height int, fullscreen bool) error
|
||||
video.width, video.height = 0, 0
|
||||
video.data = nil
|
||||
video.needUpload = false
|
||||
var m *glfw.Monitor
|
||||
|
||||
if fullscreen {
|
||||
m = glfw.GetMonitors()[settings.Current.VideoMonitorIndex]
|
||||
vms := m.GetVideoModes()
|
||||
vm := vms[len(vms)-1]
|
||||
width = vm.Width
|
||||
height = vm.Height
|
||||
}
|
||||
|
||||
video.configureWindowHints()
|
||||
|
||||
var err error
|
||||
video.Window, err = glfw.CreateWindow(width, height, video.title, m, nil)
|
||||
if err != nil {
|
||||
panic("Window creation failed:" + err.Error())
|
||||
}
|
||||
|
||||
video.Window.MakeContextCurrent()
|
||||
|
||||
// Force a minimum size for the window.
|
||||
video.Window.SetSizeLimits(160, 120, glfw.DontCare, glfw.DontCare)
|
||||
video.SetFullscreen(fullscreen)
|
||||
|
||||
video.Window.SetInputMode(glfw.CursorMode, glfw.CursorHidden)
|
||||
|
||||
if err := initGL(); err != nil {
|
||||
// Initialize Glow
|
||||
if err := gl.Init(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
@@ -496,7 +482,6 @@ func (video *Video) configureWithSize(width, height int, fullscreen bool) error
|
||||
for e := gl.GetError(); e != gl.NO_ERROR; e = gl.NO_ERROR {
|
||||
log.Printf("[Video] OpenGL error: %d\n", e)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateFilter configures the game texture filter and shader. We currently
|
||||
@@ -603,7 +588,6 @@ func (video *Video) ResetCoreState() {
|
||||
video.ensurePixelFormatDefaults()
|
||||
if video.texID != 0 {
|
||||
video.resetGameTexture(1, 1)
|
||||
video.UpdateFilter(settings.Current.VideoFilter)
|
||||
}
|
||||
video.texWidth = 0
|
||||
video.texHeight = 0
|
||||
@@ -616,8 +600,9 @@ func (video *Video) ResetCoreState() {
|
||||
video.cachedFrameValid = false
|
||||
}
|
||||
|
||||
// gameViewport fits the game to the framebuffer and records its screenshot bounds.
|
||||
func (video *Video) gameViewport(fbWidth, fbHeight int) (x, y, w, h float32) {
|
||||
// coreRatioViewport configures the vertex array to display the game at the center of the window
|
||||
// while preserving the original ascpect ratio of the game or core
|
||||
func (video *Video) coreRatioViewport(fbWidth, fbHeight, clipWidth, clipHeight int) (x, y, w, h float32) {
|
||||
// Scale the content to fit in the viewport.
|
||||
fbw := float32(fbWidth)
|
||||
fbh := float32(fbHeight)
|
||||
@@ -635,23 +620,11 @@ func (video *Video) gameViewport(fbWidth, fbHeight int) (x, y, w, h float32) {
|
||||
w = fbw
|
||||
}
|
||||
|
||||
// Keep centered, pixel-aligned bounds so the cached copy needs no resampling.
|
||||
x = float32(int32((fbw - w) / 2))
|
||||
y = float32(int32((fbh - h) / 2))
|
||||
w = fbw - 2*x
|
||||
h = fbh - 2*y
|
||||
// Place the content in the middle of the window.
|
||||
x = (fbw - w) / 2
|
||||
y = (fbh - h) / 2
|
||||
|
||||
video.presentX = int32(x)
|
||||
video.presentY = int32(y)
|
||||
video.presentW = int32(w)
|
||||
video.presentH = int32(h)
|
||||
return
|
||||
}
|
||||
|
||||
// coreRatioViewport configures the game quad's placement and core texture coordinates.
|
||||
func (video *Video) coreRatioViewport(fbWidth, fbHeight, clipWidth, clipHeight int) (x, y, w, h float32) {
|
||||
x, y, w, h = video.gameViewport(fbWidth, fbHeight)
|
||||
va := vertexArrayForFramebuffer(x, y, w, h, 1.0, float32(fbWidth), float32(fbHeight))
|
||||
va := vertexArrayForFramebuffer(x, y, w, h, 1.0, fbw, fbh)
|
||||
|
||||
if clipWidth == 0 {
|
||||
clipWidth = int(video.texWidth)
|
||||
@@ -729,7 +702,11 @@ func (video *Video) Render() {
|
||||
video.uploadTexture()
|
||||
|
||||
fbw, fbh := video.Window.GetFramebufferSize()
|
||||
_, _, w, h := video.coreRatioViewport(fbw, fbh, int(video.width), int(video.height))
|
||||
x, y, w, h := video.coreRatioViewport(fbw, fbh, int(video.width), int(video.height))
|
||||
video.presentX = int32(x)
|
||||
video.presentY = int32(y)
|
||||
video.presentW = int32(w)
|
||||
video.presentH = int32(h)
|
||||
|
||||
gl.UseProgram(video.program)
|
||||
if loc := gl.GetUniformLocation(video.program, gl.Str("color\x00")); loc >= 0 {
|
||||
@@ -746,10 +723,7 @@ func (video *Video) Render() {
|
||||
gl.BindBuffer(gl.ARRAY_BUFFER, video.vbo)
|
||||
|
||||
bindVertexArray(video.vao)
|
||||
// Preserve opaque alpha even when the core leaves its alpha channel unset.
|
||||
gl.ColorMask(true, true, true, false)
|
||||
gl.DrawArrays(gl.TRIANGLE_STRIP, 0, 4)
|
||||
gl.ColorMask(true, true, true, true)
|
||||
bindVertexArray(0)
|
||||
|
||||
// Reset MVP to identity to avoid menu issues
|
||||
@@ -780,7 +754,7 @@ func (video *Video) ensureCachedFrameTexture(width, height int32) {
|
||||
}
|
||||
}
|
||||
|
||||
// CachePresentedFrame copies the game rectangle, without borders or UI, so menu-mode
|
||||
// CachePresentedFrame copies the just-rendered frontend backbuffer so menu-mode
|
||||
// rendering does not depend on the core keeping its paused framebuffer alive.
|
||||
func (video *Video) CachePresentedFrame() {
|
||||
if !state.CoreRunning || video.width == 0 || video.height == 0 {
|
||||
@@ -788,12 +762,13 @@ func (video *Video) CachePresentedFrame() {
|
||||
return
|
||||
}
|
||||
|
||||
if video.presentW < 1 || video.presentH < 1 {
|
||||
fbw, fbh := video.Window.GetFramebufferSize()
|
||||
if fbw < 1 || fbh < 1 {
|
||||
video.cachedFrameValid = false
|
||||
return
|
||||
}
|
||||
|
||||
video.ensureCachedFrameTexture(video.presentW, video.presentH)
|
||||
video.ensureCachedFrameTexture(int32(fbw), int32(fbh))
|
||||
if video.cachedFrameTexID == 0 {
|
||||
video.cachedFrameValid = false
|
||||
return
|
||||
@@ -801,7 +776,7 @@ func (video *Video) CachePresentedFrame() {
|
||||
|
||||
gl.ActiveTexture(gl.TEXTURE0)
|
||||
gl.BindTexture(gl.TEXTURE_2D, video.cachedFrameTexID)
|
||||
gl.CopyTexSubImage2D(gl.TEXTURE_2D, 0, 0, 0, video.presentX, video.presentY, video.presentW, video.presentH)
|
||||
gl.CopyTexSubImage2D(gl.TEXTURE_2D, 0, 0, 0, 0, 0, int32(fbw), int32(fbh))
|
||||
video.cachedFrameValid = true
|
||||
}
|
||||
|
||||
@@ -815,8 +790,7 @@ func (video *Video) RenderCachedFrame() bool {
|
||||
gl.Clear(gl.COLOR_BUFFER_BIT)
|
||||
|
||||
fbw, fbh := video.Window.GetFramebufferSize()
|
||||
x, y, w, h := video.gameViewport(fbw, fbh)
|
||||
va := vertexArrayForFramebuffer(x, y, w, h, 1.0, float32(fbw), float32(fbh))
|
||||
va := vertexArrayForFramebuffer(0, 0, float32(fbw), float32(fbh), 1.0, float32(fbw), float32(fbh))
|
||||
// A backbuffer copy always has bottom-left origin, regardless of the core.
|
||||
va[3] = 0
|
||||
va[7] = 1
|
||||
@@ -827,7 +801,7 @@ func (video *Video) RenderCachedFrame() bool {
|
||||
|
||||
gl.UseProgram(video.defaultProgram)
|
||||
gl.Uniform4f(gl.GetUniformLocation(video.defaultProgram, gl.Str("color\x00")), 1, 1, 1, 1)
|
||||
gl.Uniform2f(gl.GetUniformLocation(video.defaultProgram, gl.Str("OutputSize\x00")), w, h)
|
||||
gl.Uniform2f(gl.GetUniformLocation(video.defaultProgram, gl.Str("OutputSize\x00")), float32(fbw), float32(fbh))
|
||||
gl.Uniform2f(gl.GetUniformLocation(video.defaultProgram, gl.Str("TextureSize\x00")), float32(video.cachedFrameW), float32(video.cachedFrameH))
|
||||
gl.Uniform2f(gl.GetUniformLocation(video.defaultProgram, gl.Str("InputSize\x00")), float32(video.cachedFrameW), float32(video.cachedFrameH))
|
||||
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
//go:build darwin
|
||||
// +build darwin
|
||||
|
||||
package video
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/go-gl/gl/v2.1/gl"
|
||||
"github.com/go-gl/mathgl/mgl32"
|
||||
"github.com/libretro/ludo/libretro"
|
||||
"github.com/libretro/ludo/state"
|
||||
)
|
||||
|
||||
// InitFramebuffer initializes and configures the video frame buffer based on
|
||||
// informations from the HWRenderCallback of the libretro core.
|
||||
func (video *Video) InitFramebuffer() {
|
||||
width := int32(video.Geom.MaxWidth)
|
||||
height := int32(video.Geom.MaxHeight)
|
||||
video.texWidth = width
|
||||
video.texHeight = height
|
||||
|
||||
log.Printf("[Video]: Initializing HW render (%v x %v).\n", width, height)
|
||||
|
||||
gl.ActiveTexture(gl.TEXTURE0)
|
||||
gl.BindTexture(gl.TEXTURE_2D, video.texID)
|
||||
gl.TexImage2D(gl.TEXTURE_2D, 0, gl.RGBA8, width, height, 0, video.pixType, video.pixFmt, nil)
|
||||
|
||||
gl.GenFramebuffersEXT(1, &video.fboID)
|
||||
gl.BindFramebufferEXT(gl.FRAMEBUFFER_EXT, video.fboID)
|
||||
libretro.SetCurrentFramebufferValue(uintptr(video.fboID))
|
||||
gl.FramebufferTexture2DEXT(gl.FRAMEBUFFER_EXT, gl.COLOR_ATTACHMENT0_EXT, gl.TEXTURE_2D, video.texID, 0)
|
||||
|
||||
hw := state.Core.HWRenderCallback
|
||||
if hw.Depth {
|
||||
gl.GenRenderbuffersEXT(1, &video.rboID)
|
||||
gl.BindRenderbufferEXT(gl.RENDERBUFFER_EXT, video.rboID)
|
||||
format := gl.DEPTH_COMPONENT16
|
||||
if hw.Stencil {
|
||||
format = gl.DEPTH24_STENCIL8_EXT
|
||||
}
|
||||
gl.RenderbufferStorageEXT(gl.RENDERBUFFER_EXT, uint32(format), width, height)
|
||||
gl.BindRenderbufferEXT(gl.RENDERBUFFER_EXT, 0)
|
||||
|
||||
gl.FramebufferRenderbufferEXT(gl.FRAMEBUFFER_EXT, gl.DEPTH_ATTACHMENT_EXT, gl.RENDERBUFFER_EXT, video.rboID)
|
||||
if hw.Stencil {
|
||||
gl.FramebufferRenderbufferEXT(gl.FRAMEBUFFER_EXT, gl.STENCIL_ATTACHMENT_EXT, gl.RENDERBUFFER_EXT, video.rboID)
|
||||
}
|
||||
}
|
||||
|
||||
// Default origin is top left
|
||||
video.orthoMat = mgl32.Ortho2D(-1, 1, -1, 1)
|
||||
if hw.BottomLeftOrigin {
|
||||
video.orthoMat = mgl32.Ortho2D(-1, 1, 1, -1)
|
||||
}
|
||||
|
||||
if st := gl.CheckFramebufferStatusEXT(gl.FRAMEBUFFER_EXT); st != gl.FRAMEBUFFER_COMPLETE_EXT {
|
||||
log.Fatalf("[Video] Framebuffer is not complete. Error: %v\n", st)
|
||||
}
|
||||
|
||||
gl.ClearColor(0, 0, 0, 1)
|
||||
if hw.Depth && hw.Stencil {
|
||||
gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT | gl.STENCIL_BUFFER_BIT)
|
||||
} else if hw.Depth {
|
||||
gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
|
||||
} else {
|
||||
gl.Clear(gl.COLOR_BUFFER_BIT)
|
||||
}
|
||||
}
|
||||
|
||||
func bindBackbuffer() {
|
||||
gl.BindFramebufferEXT(gl.FRAMEBUFFER_EXT, 0)
|
||||
}
|
||||
|
||||
func (video *Video) destroyFramebuffer() {
|
||||
bindBackbuffer()
|
||||
if video.rboID != 0 {
|
||||
gl.DeleteRenderbuffersEXT(1, &video.rboID)
|
||||
video.rboID = 0
|
||||
}
|
||||
if video.fboID != 0 {
|
||||
gl.DeleteFramebuffersEXT(1, &video.fboID)
|
||||
video.fboID = 0
|
||||
}
|
||||
}
|
||||
|
||||
func genVertexArrays(n int32, arrays *uint32) {
|
||||
gl.GenVertexArraysAPPLE(n, arrays)
|
||||
}
|
||||
|
||||
func bindVertexArray(array uint32) {
|
||||
gl.BindVertexArrayAPPLE(array)
|
||||
}
|
||||
@@ -1,3 +1,6 @@
|
||||
//go:build !darwin
|
||||
// +build !darwin
|
||||
|
||||
package video
|
||||
|
||||
import (
|
||||
@@ -23,26 +26,26 @@ func (video *Video) InitFramebuffer() {
|
||||
gl.BindTexture(gl.TEXTURE_2D, video.texID)
|
||||
gl.TexImage2D(gl.TEXTURE_2D, 0, gl.RGBA8, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, nil)
|
||||
|
||||
genFramebuffers(1, &video.fboID)
|
||||
bindFramebuffer(gl.FRAMEBUFFER, video.fboID)
|
||||
gl.GenFramebuffers(1, &video.fboID)
|
||||
gl.BindFramebuffer(gl.FRAMEBUFFER, video.fboID)
|
||||
libretro.SetCurrentFramebufferValue(uintptr(video.fboID))
|
||||
framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, video.texID, 0)
|
||||
gl.FramebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, video.texID, 0)
|
||||
|
||||
hw := state.Core.HWRenderCallback
|
||||
if hw.Depth {
|
||||
genRenderbuffers(1, &video.rboID)
|
||||
bindRenderbuffer(gl.RENDERBUFFER, video.rboID)
|
||||
gl.GenRenderbuffers(1, &video.rboID)
|
||||
gl.BindRenderbuffer(gl.RENDERBUFFER, video.rboID)
|
||||
format := gl.DEPTH_COMPONENT16
|
||||
if hw.Stencil {
|
||||
format = gl.DEPTH24_STENCIL8
|
||||
}
|
||||
renderbufferStorage(gl.RENDERBUFFER, uint32(format), width, height)
|
||||
bindRenderbuffer(gl.RENDERBUFFER, 0)
|
||||
gl.RenderbufferStorage(gl.RENDERBUFFER, uint32(format), width, height)
|
||||
gl.BindRenderbuffer(gl.RENDERBUFFER, 0)
|
||||
|
||||
// Separate attachment points work with both core and EXT framebuffers.
|
||||
framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, video.rboID)
|
||||
if hw.Stencil {
|
||||
framebufferRenderbuffer(gl.FRAMEBUFFER, gl.STENCIL_ATTACHMENT, gl.RENDERBUFFER, video.rboID)
|
||||
gl.FramebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, video.rboID)
|
||||
} else {
|
||||
gl.FramebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, video.rboID)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,7 +55,7 @@ func (video *Video) InitFramebuffer() {
|
||||
video.orthoMat = mgl32.Ortho2D(-1, 1, 1, -1)
|
||||
}
|
||||
|
||||
if st := checkFramebufferStatus(gl.FRAMEBUFFER); st != gl.FRAMEBUFFER_COMPLETE {
|
||||
if st := gl.CheckFramebufferStatus(gl.FRAMEBUFFER); st != gl.FRAMEBUFFER_COMPLETE {
|
||||
log.Fatalf("[Video] Framebuffer is not complete. Error: %v\n", st)
|
||||
}
|
||||
|
||||
@@ -67,17 +70,25 @@ func (video *Video) InitFramebuffer() {
|
||||
}
|
||||
|
||||
func bindBackbuffer() {
|
||||
bindFramebuffer(gl.FRAMEBUFFER, 0)
|
||||
gl.BindFramebuffer(gl.FRAMEBUFFER, 0)
|
||||
}
|
||||
|
||||
func (video *Video) destroyFramebuffer() {
|
||||
bindBackbuffer()
|
||||
if video.rboID != 0 {
|
||||
deleteRenderbuffers(1, &video.rboID)
|
||||
gl.DeleteRenderbuffers(1, &video.rboID)
|
||||
video.rboID = 0
|
||||
}
|
||||
if video.fboID != 0 {
|
||||
deleteFramebuffers(1, &video.fboID)
|
||||
gl.DeleteFramebuffers(1, &video.fboID)
|
||||
video.fboID = 0
|
||||
}
|
||||
}
|
||||
|
||||
func genVertexArrays(n int32, arrays *uint32) {
|
||||
gl.GenVertexArrays(n, arrays)
|
||||
}
|
||||
|
||||
func bindVertexArray(array uint32) {
|
||||
gl.BindVertexArray(array)
|
||||
}
|
||||
Reference in new issue
Block a user