Compare commits

..
4 Commits
Author SHA1 Message Date
dependabot[bot] 7a2dc452c7 Bump github.com/klauspost/compress from 1.18.5 to 1.18.7 (#598)
Bumps [github.com/klauspost/compress](https://github.com/klauspost/compress) from 1.18.5 to 1.18.7.
- [Release notes](https://github.com/klauspost/compress/releases)
- [Commits](https://github.com/klauspost/compress/compare/v1.18.5...v1.18.7)

---
updated-dependencies:
- dependency-name: github.com/klauspost/compress
  dependency-version: 1.18.7
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-22 14:59:26 +02:00
Jean-André SantoniandHigor Eurípedes c47ad16a70 Support GL cores (#91)
* Improve the libretro binding to support GL cores

* Start implementing InitFramebuffer

* TODO

* Try to implement ProcAddress

* Fix display of non gl cores

* Implement more of InitFramebuffer

* Move InitFramebuffer and call HWRenderCallback.ContextReset

* Comment

* Call vid.InitFramebuffer and Core.HWRenderCallback.ContextReset during LoadGame

* Remove useless code

* List hardware contexts in the libretro binding

* Update dependency for GetProcAddress

* Fix framebuffer creation

* Fix gl.Clear

* Cast C.retro_hw_context_reset_t

* Set c.get_current_framebuffer and c.get_proc_address properly

* Lint fix

* Fix compat with non GL cores

* Debug

* Fix an important memory bug

* Various rendering improvements

* Small fix

* Active texture fixes

* Unbind vertex array

* OpenGL core support improvements (#308)

* Fix sample batch callback and OpenGL core support

* Fix texture/framebuffer allocation and clipping

The libretro api requires the framebuffers to be at least as large as
max_width/max_height.

* Fix context destruction

Fixes Flycast

* Fix sharp-bilinear and zfast-crt filters (#311)

TextureSize uniform was receiving the input size instead of the actual texture
size.

* Call gl.TexSubImage2D after binding the texture

* Fix shader live update

* Temporarily fix tests

* Fix FBO on OSX (#313)

* Fix FBO on OSX

* Remove useless code

* Use GL v2.1 for fonts drawing

* Fix fonts on OSX with GL 2.1

* Rename

* Same thing for GenVertexArrays

* Make fl0w work on OSX

* Implement libretro.EnvironmentGetPrefferedHWRenderer

* Uncomment gl.Enable(gl.TEXTURE_2D)

* Fixes

* Fix

* Fix

* Forgot to use program before get uniform

* Fix swanstation

* Start with a clean context

* Return the fb from C

* Shared context

* Reconfigure hints

* Harden texture size

* Fix fullscreen toggle on flycast

* Reduce font.go churn

* Reduce font.go churn

* Fix cleaning the display after closing the core

* Fix order of destoy

* Fix dolphin

* Launch more GL cores (#591)

* lrps2 booting

* Fix viewport

* Fix sampler

* Refactor

* Fix screenshots

* Fix dolphin screenshots

* Fixes

* Fix launching swanstation with -L

* Isolate core GL contexts

* Fix software frame uploads

* Fix libco

* Simplify screenshot sizing

* Add more GL cores

* Clean up frame capture

* Fix macOS GL profiles

* Preserve contexts on fullscreen toggle

* Fix screenshot cropping after resize

* Fix texture state on core switches

* Handle unsupported GL contexts

* Save states despite thumbnail errors

* Fix core loading error check

* Fix notification scaling

* Handle empty core options

---------

Co-authored-by: Higor Eurípedes <[email protected]>
2026-10-22 14:58:24 +02:00
Jean-André Santoni 13347e0051 Fix CI (#597) 2026-10-19 15:45:44 +02:00
dependabot[bot] 31951498f7 Bump golang.org/x/image from 0.39.0 to 0.41.0 (#593)
Bumps [golang.org/x/image](https://github.com/golang/image) from 0.39.0 to 0.41.0.
- [Commits](https://github.com/golang/image/compare/v0.39.0...v0.41.0)

---
updated-dependencies:
- dependency-name: golang.org/x/image
  dependency-version: 0.41.0
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-19 23:16:25 +07:00
27 changed files with 291 additions and 311 deletions

No files matched your search

+12 -9
View File
@@ -11,19 +11,22 @@ ifeq ($(OS), Linux)
BUNDLENAME = $(APP)-$(OS)-$(DISPDRIVER)-$(ARCH)-$(VERSION)
endif
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
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
ifeq ($(ARCH), arm)
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))
CORES := $(filter-out citra dolphin flycast mednafen_pcfx mednafen_saturn melonds mupen64plus_next pcsx2 ppsspp swanstation,$(CORES))
endif
ifeq ($(ARCH), arm64)
CORES := $(filter-out mednafen_saturn,$(CORES))
CORES := $(filter-out mupen64plus_next,$(CORES))
CORES := $(filter-out picodrive,$(CORES))
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
endif
DYLIBS = $(addprefix cores/, $(addsuffix _libretro.dylib,$(CORES)))
+1 -1
View File
@@ -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 is able to launch most non GL libretro cores.
It supports software-rendered and desktop OpenGL libretro cores. Hardware-rendered cores may require a newer OpenGL version than the frontend itself.
It works on OSX, Linux, Linux ARM and Windows. You can download releases [here](https://github.com/libretro/ludo/releases)
+37 -5
View File
@@ -1,10 +1,10 @@
package core
import (
"log"
"os"
"path/filepath"
"reflect"
"runtime"
"strings"
"testing"
@@ -15,6 +15,34 @@ 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 }
@@ -201,17 +229,21 @@ func Test_coreLoadGame(t *testing.T) {
Init(&video.Video{})
if err := glfw.Init(); err != nil {
log.Fatalln("failed to initialize glfw")
var initErr error
onMainThread(func() { initErr = glfw.Init() })
if initErr != nil {
t.Fatalf("glfw.Init() error = %v", initErr)
}
t.Cleanup(glfw.Terminate)
t.Cleanup(func() { onMainThread(glfw.Terminate) })
if err := Load("testdata/vecx_libretro" + ext); err != nil {
t.Fatalf("Load() error = %v", err)
}
var loadGameErr error
got := utils.CaptureOutput(func() { loadGameErr = LoadGame("testdata/Polar Rescue (USA).vec") })
got := utils.CaptureOutput(func() {
onMainThread(func() { loadGameErr = LoadGame("testdata/Polar Rescue (USA).vec") })
})
if loadGameErr != nil {
t.Fatalf("LoadGame() error = %v", loadGameErr)
}
+8 -3
View File
@@ -151,13 +151,18 @@ 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 {
vid.ReconfigureHWContext(settings.Current.VideoFullscreen)
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
}
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.
+3 -3
View File
@@ -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.39.0
golang.org/x/image v0.41.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.5 // indirect
github.com/klauspost/compress v1.18.7 // 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.36.0 // indirect
golang.org/x/text v0.37.0 // indirect
)
go 1.25.0
+6 -6
View File
@@ -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.5 h1:/h1gH5Ce+VWNLSWqPzOVn6XBO+vJbCNGvjoaGBFW2IE=
github.com/klauspost/compress v1.18.5/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ=
github.com/klauspost/compress v1.18.7 h1:aUyZsS4kH3QTKurYhAOwAHxllVPnOthb3vPfnF1Ehjw=
github.com/klauspost/compress v1.18.7/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.39.0 h1:skVYidAEVKgn8lZ602XO75asgXBgLj9G/FE3RbuPFww=
golang.org/x/image v0.39.0/go.mod h1:sIbmppfU+xFLPIG0FoVUTvyBMmgng1/XAMhQ2ft0hpA=
golang.org/x/image v0.41.0 h1:8wS72eGJMJaBxK6okTzd4WaXumUlTVlb753MlsSvTCo=
golang.org/x/image v0.41.0/go.mod h1:uIc348UZMSvS5Z65CVZ7iDPaNobNFEPeJ4kbqTOszmA=
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.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg=
golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164=
golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc=
golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38=
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=
+13 -21
View File
@@ -467,20 +467,19 @@ type (
)
var (
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()
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()
)
// SetContextCallbacks hands the current graphics context between the calling
@@ -623,7 +622,6 @@ func (core *Core) Deinit() {
inputState = nil
log = nil
getTimeUsec = nil
getCurrentFramebuffer = nil
getProcAddress = nil
}
@@ -874,11 +872,6 @@ 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)))
@@ -1061,7 +1054,6 @@ 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)
+2 -2
View File
@@ -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)
cerr := C.dlerror()
if h == nil || cerr != nil {
if h == nil {
cerr := C.dlerror()
err := C.GoString(cerr)
return nil, errors.New(err)
}
+9 -6
View File
@@ -52,16 +52,20 @@ 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()
if !state.MenuActive {
menuActive := state.MenuActive
if menuActive {
m.Update(dt)
}
if state.MenuContextResetNeeded {
m.ContextReset()
state.MenuContextResetNeeded = false
}
if !menuActive {
if state.CoreRunning {
vid.BeginCore()
state.Core.Run()
@@ -82,7 +86,6 @@ func runLoop(vid *video.Video, m *menu.Menu) {
savefiles.SaveSRAM()
}
} else {
m.Update(dt)
vid.BeginFrontendFrame()
vid.RenderPresentedFrame()
fulfillPendingScreenshot(vid)
+1 -1
View File
@@ -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.ReconfigureVideo(settings.Current.VideoFullscreen)
m.SetFullscreen(settings.Current.VideoFullscreen)
err := settings.Save()
if err != nil {
ntf.DisplayAndLogf(ntf.Error, "Menu", "Error saving settings: %v", err)
+5 -18
View File
@@ -6,8 +6,6 @@ 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"
@@ -104,24 +102,13 @@ func (m *Menu) ContextReset() {
m.icons[filename] = video.NewImage(path)
}
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())
// 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
}
}
m.ContextReset()
}
// WarpToQuickMenu loads the contextual menu for games that are launched from
+1
View File
@@ -11,6 +11,7 @@ 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
+1 -1
View File
@@ -16,7 +16,7 @@ func buildCoreOptions() Scene {
var list sceneCoreOptions
list.label = "Core Options"
if core.Options == nil {
if core.Options == nil || len(core.Options.Vars) == 0 {
list.children = append(list.children, entry{
label: "No options",
icon: "subsetting",
-1
View File
@@ -30,7 +30,6 @@ 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 {
+2 -2
View File
@@ -164,7 +164,7 @@ var incrCallbacks = map[string]callbackIncrement{
v := f.Value().(bool)
v = !v
f.Set(v)
menu.ReconfigureVideo(settings.Current.VideoFullscreen)
menu.SetFullscreen(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.ReconfigureVideo(settings.Current.VideoFullscreen)
menu.SetFullscreen(settings.Current.VideoFullscreen)
settings.Save()
},
"VideoFilter": func(f *structs.Field, direction int) {
+7 -3
View File
@@ -50,13 +50,17 @@ func New(vars []VariableInterface) (*Options, error) {
// Cache core options
for _, v := range vars {
v := v
choices := v.Choices()
// A core may expose an empty selector, e.g. when no BIOS files are found.
if len(choices) == 0 {
continue
}
o.Vars = append(o.Vars, &Variable{
Key: v.Key(),
Desc: v.Desc(),
Choices: v.Choices(),
Choices: choices,
Default: v.DefaultValue(),
Choice: utils.IndexOfString(v.DefaultValue(), v.Choices()),
Choice: utils.IndexOfString(v.DefaultValue(), choices),
})
}
o.Updated = true
+1 -10
View File
@@ -33,7 +33,6 @@ 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
@@ -117,15 +116,7 @@ func applyIPS(patch, source []byte) (*[]byte, error) {
offset++
if address == EOF {
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++
if offset == len(patch) || offset == len(patch)-3 {
return &targetData, nil
}
}
+6
View File
@@ -43,13 +43,17 @@ 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",
@@ -60,6 +64,8 @@ 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",
+3 -1
View File
@@ -32,7 +32,7 @@ func TestHardwareContextIsolation(t *testing.T) {
}
defer w.Destroy()
w.MakeContextCurrent()
if err := gl.Init(); err != nil {
if err := initGL(); err != nil {
t.Fatal(err)
}
t.Log("GL:", gl.GoStr(gl.GetString(gl.VERSION)))
@@ -138,6 +138,8 @@ 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)
-5
View File
@@ -4,14 +4,9 @@ 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;
@@ -1,6 +1,3 @@
//go:build !darwin
// +build !darwin
package video
import (
@@ -26,26 +23,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)
gl.GenFramebuffers(1, &video.fboID)
gl.BindFramebuffer(gl.FRAMEBUFFER, video.fboID)
genFramebuffers(1, &video.fboID)
bindFramebuffer(gl.FRAMEBUFFER, video.fboID)
libretro.SetCurrentFramebufferValue(uintptr(video.fboID))
gl.FramebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, video.texID, 0)
framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, video.texID, 0)
hw := state.Core.HWRenderCallback
if hw.Depth {
gl.GenRenderbuffers(1, &video.rboID)
gl.BindRenderbuffer(gl.RENDERBUFFER, video.rboID)
genRenderbuffers(1, &video.rboID)
bindRenderbuffer(gl.RENDERBUFFER, video.rboID)
format := gl.DEPTH_COMPONENT16
if hw.Stencil {
format = gl.DEPTH24_STENCIL8
}
gl.RenderbufferStorage(gl.RENDERBUFFER, uint32(format), width, height)
gl.BindRenderbuffer(gl.RENDERBUFFER, 0)
renderbufferStorage(gl.RENDERBUFFER, uint32(format), width, height)
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 {
gl.FramebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, gl.RENDERBUFFER, video.rboID)
} else {
gl.FramebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, video.rboID)
framebufferRenderbuffer(gl.FRAMEBUFFER, gl.STENCIL_ATTACHMENT, gl.RENDERBUFFER, video.rboID)
}
}
@@ -55,7 +52,7 @@ func (video *Video) InitFramebuffer() {
video.orthoMat = mgl32.Ortho2D(-1, 1, 1, -1)
}
if st := gl.CheckFramebufferStatus(gl.FRAMEBUFFER); st != gl.FRAMEBUFFER_COMPLETE {
if st := checkFramebufferStatus(gl.FRAMEBUFFER); st != gl.FRAMEBUFFER_COMPLETE {
log.Fatalf("[Video] Framebuffer is not complete. Error: %v\n", st)
}
@@ -70,25 +67,17 @@ func (video *Video) InitFramebuffer() {
}
func bindBackbuffer() {
gl.BindFramebuffer(gl.FRAMEBUFFER, 0)
bindFramebuffer(gl.FRAMEBUFFER, 0)
}
func (video *Video) destroyFramebuffer() {
bindBackbuffer()
if video.rboID != 0 {
gl.DeleteRenderbuffers(1, &video.rboID)
deleteRenderbuffers(1, &video.rboID)
video.rboID = 0
}
if video.fboID != 0 {
gl.DeleteFramebuffers(1, &video.fboID)
deleteFramebuffers(1, &video.fboID)
video.fboID = 0
}
}
func genVertexArrays(n int32, arrays *uint32) {
gl.GenVertexArrays(n, arrays)
}
func bindVertexArray(array uint32) {
gl.BindVertexArray(array)
}
+63
View File
@@ -0,0 +1,63 @@
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
}
+10 -38
View File
@@ -1,6 +1,7 @@
package video
import (
"errors"
"image"
"image/png"
"os"
@@ -10,42 +11,21 @@ import (
"github.com/go-gl/gl/v2.1/gl"
"github.com/libretro/ludo/settings"
"github.com/libretro/ludo/state"
)
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()
outW, outH := video.Geom.BaseWidth, video.Geom.BaseHeight
if outW < 1 || outH < 1 {
return errors.New("invalid screenshot output size")
}
x := video.presentX
y := video.presentY
w := video.presentW
h := video.presentH
if w < 1 || h < 1 {
fbw, fbh := video.Window.GetFramebufferSize()
x, y, w, h = int32(0), int32(0), int32(fbw), int32(fbh)
if !state.CoreRunning || video.width < 1 || video.height < 1 || w < 1 || h < 1 {
return errors.New("no game frame to capture")
}
var oldReadBuffer int32
@@ -58,8 +38,8 @@ func (video *Video) captureBackbufferScreenshot(name string) error {
gl.ReadBuffer(uint32(oldReadBuffer))
flipped := imaging.FlipV(img)
if int32(flipped.Bounds().Dx()) != outW || int32(flipped.Bounds().Dy()) != outH {
flipped = imaging.Resize(flipped, int(outW), int(outH), imaging.Linear)
if flipped.Bounds().Dx() != outW || flipped.Bounds().Dy() != outH {
flipped = imaging.Resize(flipped, outW, outH, imaging.Linear)
}
err := os.MkdirAll(settings.Current.ScreenshotsDirectory, os.ModePerm)
@@ -77,14 +57,6 @@ 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 {
+4 -1
View File
@@ -23,6 +23,9 @@ 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
@@ -57,7 +60,7 @@ func newProgram(vertexShaderSource, fragmentShaderSource string) (uint32, error)
func compileShader(source string, shaderType uint32) (uint32, error) {
shader := gl.CreateShader(shaderType)
csources, free := gl.Strs(source)
csources, free := gl.Strs(glslVersion + source)
gl.ShaderSource(shader, 1, csources, nil)
free()
gl.CompileShader(shader)
+1 -1
View File
@@ -29,7 +29,7 @@ func TestSoftwareFrameUploadBounds(t *testing.T) {
}
defer w.Destroy()
w.MakeContextCurrent()
if err := gl.Init(); err != nil {
if err := initGL(); err != nil {
t.Fatal(err)
}
+81 -55
View File
@@ -4,6 +4,7 @@
package video
import (
"errors"
"log"
"path/filepath"
"unsafe"
@@ -26,6 +27,9 @@ 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
@@ -78,19 +82,29 @@ func Init(fullscreen bool) *Video {
return vid
}
// 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()
// 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)
}
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) {
func (video *Video) ReconfigureHWContext(fullscreen bool) error {
width := 384 * 2
height := 240 * 2
if video.Window != nil && !fullscreen {
@@ -102,12 +116,7 @@ func (video *Video) ReconfigureHWContext(fullscreen bool) {
height = 240 * 2
}
}
if video.Window != nil {
video.RunContextDestroy()
video.DestroySharedHWContext()
video.Window.Destroy()
}
video.configureWithSize(width, height, fullscreen)
return video.configureWithSize(width, height, fullscreen)
}
// CreateSharedHWContext creates a hidden context shared with the visible frontend context.
@@ -365,10 +374,33 @@ func (video *Video) resetGameTexture(width, height int32) {
// Configure instanciates the video package
func (video *Video) Configure(fullscreen bool) {
video.configureWithSize(384*2, 240*2, fullscreen)
if err := video.configureWithSize(384*2, 240*2, fullscreen); err != nil {
panic("Window creation failed:" + err.Error())
}
}
func (video *Video) configureWithSize(width, height int, fullscreen bool) {
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
// Object names from the old share group must not be reused in this context.
video.texID = 0
video.cachedFrameTexID = 0
@@ -377,33 +409,15 @@ func (video *Video) configureWithSize(width, height int, fullscreen bool) {
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)
// Initialize Glow
if err := gl.Init(); err != nil {
if err := initGL(); err != nil {
panic(err)
}
@@ -482,6 +496,7 @@ func (video *Video) configureWithSize(width, height int, fullscreen bool) {
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
@@ -588,6 +603,7 @@ 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
@@ -600,9 +616,8 @@ func (video *Video) ResetCoreState() {
video.cachedFrameValid = false
}
// 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) {
// gameViewport fits the game to the framebuffer and records its screenshot bounds.
func (video *Video) gameViewport(fbWidth, fbHeight int) (x, y, w, h float32) {
// Scale the content to fit in the viewport.
fbw := float32(fbWidth)
fbh := float32(fbHeight)
@@ -620,11 +635,23 @@ func (video *Video) coreRatioViewport(fbWidth, fbHeight, clipWidth, clipHeight i
w = fbw
}
// Place the content in the middle of the window.
x = (fbw - w) / 2
y = (fbh - h) / 2
// 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
va := vertexArrayForFramebuffer(x, y, w, h, 1.0, fbw, fbh)
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))
if clipWidth == 0 {
clipWidth = int(video.texWidth)
@@ -702,11 +729,7 @@ func (video *Video) Render() {
video.uploadTexture()
fbw, fbh := video.Window.GetFramebufferSize()
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)
_, _, w, h := video.coreRatioViewport(fbw, fbh, int(video.width), int(video.height))
gl.UseProgram(video.program)
if loc := gl.GetUniformLocation(video.program, gl.Str("color\x00")); loc >= 0 {
@@ -723,7 +746,10 @@ 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
@@ -754,7 +780,7 @@ func (video *Video) ensureCachedFrameTexture(width, height int32) {
}
}
// CachePresentedFrame copies the just-rendered frontend backbuffer so menu-mode
// CachePresentedFrame copies the game rectangle, without borders or UI, 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 {
@@ -762,13 +788,12 @@ func (video *Video) CachePresentedFrame() {
return
}
fbw, fbh := video.Window.GetFramebufferSize()
if fbw < 1 || fbh < 1 {
if video.presentW < 1 || video.presentH < 1 {
video.cachedFrameValid = false
return
}
video.ensureCachedFrameTexture(int32(fbw), int32(fbh))
video.ensureCachedFrameTexture(video.presentW, video.presentH)
if video.cachedFrameTexID == 0 {
video.cachedFrameValid = false
return
@@ -776,7 +801,7 @@ func (video *Video) CachePresentedFrame() {
gl.ActiveTexture(gl.TEXTURE0)
gl.BindTexture(gl.TEXTURE_2D, video.cachedFrameTexID)
gl.CopyTexSubImage2D(gl.TEXTURE_2D, 0, 0, 0, 0, 0, int32(fbw), int32(fbh))
gl.CopyTexSubImage2D(gl.TEXTURE_2D, 0, 0, 0, video.presentX, video.presentY, video.presentW, video.presentH)
video.cachedFrameValid = true
}
@@ -790,7 +815,8 @@ func (video *Video) RenderCachedFrame() bool {
gl.Clear(gl.COLOR_BUFFER_BIT)
fbw, fbh := video.Window.GetFramebufferSize()
va := vertexArrayForFramebuffer(0, 0, float32(fbw), float32(fbh), 1.0, float32(fbw), float32(fbh))
x, y, w, h := video.gameViewport(fbw, fbh)
va := vertexArrayForFramebuffer(x, y, w, h, 1.0, float32(fbw), float32(fbh))
// A backbuffer copy always has bottom-left origin, regardless of the core.
va[3] = 0
va[7] = 1
@@ -801,7 +827,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")), float32(fbw), float32(fbh))
gl.Uniform2f(gl.GetUniformLocation(video.defaultProgram, gl.Str("OutputSize\x00")), w, h)
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))
-93
View File
@@ -1,93 +0,0 @@
//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)
}