Compare commits

...
63 Commits
Author SHA1 Message Date
Jean-André Santoni 5798e14105 Clean 2026-05-17 23:13:31 +02:00
Jean-André Santoni 99f215025b Add back context destroy 2026-05-17 23:03:46 +02:00
Jean-André Santoni 1bd4ad6986 Support GL cores on Linux 2026-05-17 22:22:25 +02:00
Jean-André Santoni 4795525132 Merge origin/master into kivutar/gl 2026-05-17 16:30:56 +02:00
Jean-André Santoni 3edf65f9fd Merge remote-tracking branch 'origin/master' into kivutar/gl 2022-08-05 11:53:07 +02:00
Jean-André Santoni e01cc6bb86 Merge remote-tracking branch 'origin/master' into kivutar/gl 2022-02-10 21:25:25 +01:00
Jean-André Santoni 3e084bf7f1 Merge remote-tracking branch 'origin/master' into kivutar/gl 2021-12-14 11:05:13 +01:00
Jean-André Santoni 2bcbb1d24c Merge remote-tracking branch 'origin/master' into kivutar/gl 2021-11-24 13:12:40 +07:00
Jean-André Santoni 7d48bb840b Merge remote-tracking branch 'origin/master' into kivutar/gl 2021-11-14 10:12:38 +07:00
Jean-André Santoni cb8649a129 Forgot to use program before get uniform 2021-10-22 20:47:24 +07:00
Jean-André Santoni d74969f50e Fix 2021-10-22 20:09:45 +07:00
Jean-André Santoni a95767339e Merge remote-tracking branch 'origin/master' into kivutar/gl 2021-10-22 19:47:59 +07:00
Jean-André Santoni 346dbd8a33 Merge remote-tracking branch 'origin/master' into kivutar/gl 2021-09-30 14:24:57 +07:00
Jean-André Santoni c95a3a7a14 Merge branch 'master' into kivutar/gl 2021-05-14 19:59:41 +07:00
Jean-André Santoni 8647e6f893 Merge branch 'master' into kivutar/gl 2021-05-14 11:09:32 +07:00
Jean-André Santoni a43843bab1 Fix 2021-04-17 15:40:33 +07:00
Jean-André Santoni 6097d396df Merge branch 'master' into kivutar/gl 2021-04-17 14:56:26 +07:00
Jean-André Santoni e4c5845b57 Merge branch 'master' into kivutar/gl 2021-01-28 10:57:07 +07:00
Jean-André Santoni da0092ef53 Fixes 2021-01-05 22:38:01 +07:00
Jean-André Santoni d8afc6031a Merge remote-tracking branch 'origin/master' into kivutar/gl 2021-01-05 22:33:41 +07:00
Jean-André Santoni 1e0c4a0966 Uncomment gl.Enable(gl.TEXTURE_2D) 2020-07-27 00:10:20 +07:00
Jean-André Santoni e01a76b08d Merge remote-tracking branch 'origin/master' into kivutar/gl 2020-07-26 23:35:51 +07:00
Jean-André Santoni 73d70cb1f9 Implement libretro.EnvironmentGetPrefferedHWRenderer 2020-03-01 17:37:04 +07:00
Jean-André Santoni 8b8d49eef4 Merge remote-tracking branch 'origin/master' into kivutar/gl 2020-03-01 17:24:24 +07:00
Jean-André Santoni 008a70c851 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
2020-02-29 15:40:18 +07:00
Jean-André Santoni 1d8f8e8fac Merge remote-tracking branch 'origin/master' into kivutar/gl 2020-02-29 12:25:12 +07:00
Jean-André Santoni 1146cb6f99 Merge remote-tracking branch 'origin/master' into kivutar/gl 2020-02-26 21:17:16 +07:00
Jean-André Santoni dbfbe3ace5 Temporarily fix tests 2020-02-24 19:25:47 +07:00
Jean-André Santoni e35cba220c Fix shader live update 2020-02-24 18:56:29 +07:00
Jean-André Santoni f32af42ba9 Call gl.TexSubImage2D after binding the texture 2020-02-24 18:42:12 +07:00
Higor Eurípedes 827ae5461d Fix sharp-bilinear and zfast-crt filters (#311)
TextureSize uniform was receiving the input size instead of the actual texture
size.
2020-02-24 04:23:24 +07:00
Jean-André Santoni 40396a4b86 Merge remote-tracking branch 'origin/master' into kivutar/gl 2020-02-07 10:09:37 +07:00
Higor Eurípedes ff9a3c8f4b 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
2020-02-07 09:50:07 +07:00
Jean-André Santoni b8144699f6 Unbind vertex array 2019-11-24 21:02:46 +07:00
Jean-André Santoni 42d43d63c3 Active texture fixes 2019-11-24 20:56:12 +07:00
Jean-André Santoni 9c5c3f5962 Small fix 2019-11-24 20:52:40 +07:00
Jean-André Santoni 2dd9150510 Various rendering improvements 2019-11-24 20:35:25 +07:00
Jean-André Santoni d127589d50 Fix an important memory bug 2019-11-22 12:05:28 +07:00
Jean-André Santoni 05adbe4f6a Debug 2019-11-22 11:11:09 +07:00
Jean-André Santoni ab13ef0ebc Fix compat with non GL cores 2019-11-22 10:56:19 +07:00
Jean-André Santoni 8f51dd3b2d Merge remote-tracking branch 'origin/master' into kivutar/gl 2019-11-22 10:45:16 +07:00
Jean-André Santoni 4618fb63bf Merge remote-tracking branch 'origin/master' into kivutar/gl 2019-10-31 11:51:50 +07:00
Jean-André Santoni 2d55e97431 Lint fix 2019-10-27 12:39:57 +07:00
Jean-André Santoni c7030756e3 Set c.get_current_framebuffer and c.get_proc_address properly 2019-10-27 12:31:28 +07:00
Jean-André Santoni 614c5c2ed9 Merge remote-tracking branch 'origin/master' into kivutar/gl 2019-10-27 11:26:52 +07:00
Jean-André Santoni 466c2ef696 Cast C.retro_hw_context_reset_t 2019-10-27 11:21:17 +07:00
Jean-André Santoni 4914b16606 Fix gl.Clear 2019-10-26 17:20:20 +07:00
Jean-André Santoni 341ad35519 Fix framebuffer creation 2019-10-26 17:14:40 +07:00
Jean-André Santoni 933c86b852 Update dependency for GetProcAddress 2019-10-08 10:55:36 +07:00
Jean-André Santoni 681dfbc6ab Merge branch 'master' into kivutar/gl 2019-10-08 09:51:48 +07:00
Jean-André Santoni f241ef4995 List hardware contexts in the libretro binding 2019-03-12 23:58:12 +07:00
Jean-André Santoni 6e4b4b634e Remove useless code 2019-03-12 21:32:28 +07:00
Jean-André Santoni 87175ca350 Call vid.InitFramebuffer and Core.HWRenderCallback.ContextReset during LoadGame 2019-03-12 19:58:20 +07:00
Jean-André Santoni 7eef38cf2e Comment 2019-03-12 19:00:31 +07:00
Jean-André Santoni dc56ebfff9 Merge branch 'master' into kivutar/gl 2019-03-12 18:52:09 +07:00
Jean-André Santoni d3fd407e77 Move InitFramebuffer and call HWRenderCallback.ContextReset 2019-02-22 16:32:59 +07:00
Jean-André Santoni a7cf8d1f99 Implement more of InitFramebuffer 2019-02-22 16:27:24 +07:00
Jean-André Santoni a7da8f7cb9 Fix display of non gl cores 2019-02-22 16:18:06 +07:00
Jean-André Santoni 967b0da04f Merge remote-tracking branch 'origin/master' into kivutar/gl 2019-02-22 15:55:55 +07:00
Jean-André Santoni c6c0c9c819 Try to implement ProcAddress 2018-12-30 13:03:25 +07:00
Jean-André Santoni d41ab5d9d7 TODO 2018-12-30 12:38:54 +07:00
Jean-André Santoni 7e794694f5 Start implementing InitFramebuffer 2018-12-30 12:33:24 +07:00
Jean-André Santoni d861e3d47d Improve the libretro binding to support GL cores 2018-12-30 11:46:35 +07:00
12 changed files with 880 additions and 133 deletions

No files matched your search

+21 -1
View File
@@ -20,6 +20,7 @@ import (
"github.com/libretro/ludo/options"
"github.com/libretro/ludo/patch"
"github.com/libretro/ludo/savefiles"
"github.com/libretro/ludo/settings"
"github.com/libretro/ludo/state"
"github.com/libretro/ludo/video"
@@ -51,8 +52,11 @@ func Load(sofile string) error {
if err != nil {
return err
}
state.Core.SetEnvironment(environment)
state.Core.Init()
state.CoreSetSharedContext = false
vid.SetHWRenderContext(nil)
state.Core.SetVideoRefresh(vid.Refresh)
state.Core.SetInputPoll(func() {})
state.Core.SetInputState(input.State)
@@ -264,13 +268,19 @@ func LoadGame(gamePath string) error {
vid.SetTitle("Ludo - " + si.LibraryName)
}
if state.Core.HWRenderCallback != nil {
vid.SetHWRenderContext(state.Core.HWRenderCallback)
vid.Reconfigure(settings.Current.VideoFullscreen)
} else if vid.Window == nil {
vid.Configure(settings.Current.VideoFullscreen)
}
input.Init(vid)
audio.Reconfigure(int32(avi.Timing.SampleRate))
if state.Core.AudioCallback != nil {
state.Core.AudioCallback.SetState(true)
}
state.CoreRunning = true
state.FastForward = false
state.GamePath = gamePath
@@ -283,6 +293,15 @@ func LoadGame(gamePath string) error {
log.Println("[Core]: Game loaded: " + gamePath)
savefiles.LoadSRAM()
if state.Core.HWRenderCallback != nil {
vid.MakeHardwareContextCurrent()
vid.InitFramebuffer()
vid.PrepareCoreContext()
state.Core.HWRenderCallback.ContextReset()
}
state.CoreRunning = true
return nil
}
@@ -293,6 +312,7 @@ func Unload() {
state.Core.Deinit()
state.CorePath = ""
state.Core = nil
state.CoreSetSharedContext = false
Options = nil
}
}
+20
View File
@@ -143,6 +143,23 @@ func environment(cmd uint32, data unsafe.Pointer) bool {
state.Core.SetFrameTimeCallback(data)
case libretro.EnvironmentSetAudioCallback:
state.Core.SetAudioCallback(data)
case libretro.EnvironmentSetHWRender:
state.Core.HWRenderCallback = libretro.SetHWRenderCallback(
data,
vid.CurrentFramebuffer,
vid.ProcAddress)
log.Printf(
"[Env]: HWContextType: %d Version: %d.%d Debug: %t",
state.Core.HWRenderCallback.HWContextType,
state.Core.HWRenderCallback.VersionMajor,
state.Core.HWRenderCallback.VersionMinor,
state.Core.HWRenderCallback.DebugContext,
)
return true
case libretro.EnvironmentSetHWSharedContext:
state.CoreSetSharedContext = true
log.Println("[Env]: SET_HW_SHARED_CONTEXT")
return true
case libretro.EnvironmentGetCanDupe:
libretro.SetBool(data, true)
case libretro.EnvironmentSetPixelFormat:
@@ -151,6 +168,9 @@ func environment(cmd uint32, data unsafe.Pointer) bool {
return environmentGetSystemDirectory(data)
case libretro.EnvironmentGetSaveDirectory:
return environmentGetSaveDirectory(data)
case libretro.EnvironmentGetPrefferedHWRender:
log.Println("[Env]: EnvironmentGetPrefferedHWRenderer:", libretro.HWContextOpenGL)
libretro.SetUint(data, uint(libretro.HWContextOpenGL))
case libretro.EnvironmentShutdown:
vid.SetShouldClose(true)
case libretro.EnvironmentGetCoreOptionsVersion:
+23
View File
@@ -6,6 +6,7 @@ package libretro
#include <stdarg.h>
#include <stdio.h>
#include <pthread.h>
#include <stdint.h>
#ifdef __APPLE__
#include <mach/semaphore.h>
@@ -49,6 +50,7 @@ static pthread_t s_thread;
static SEM_T s_sem_do;
static SEM_T s_sem_done;
static bool s_use_thread = false;
static uintptr_t s_current_framebuffer = 0;
void* emu_thread_loop(void *a0) {
print_sema("begin thread\n");
@@ -131,6 +133,14 @@ void bridge_retro_frame_time_callback(retro_frame_time_callback_t f, retro_usec_
f(usec);
}
void bridge_retro_hw_context_reset(retro_hw_context_reset_t f) {
f();
}
void bridge_retro_hw_context_destroy(retro_hw_context_reset_t f) {
f();
}
void bridge_retro_audio_callback(retro_audio_callback_t f) {
f();
}
@@ -303,5 +313,18 @@ int64_t coreGetTimeUsec_cgo() {
return coreGetTimeUsec();
}
uintptr_t coreGetCurrentFramebuffer_cgo() {
return s_current_framebuffer;
}
void coreSetCurrentFramebuffer_cgo(uintptr_t fb) {
s_current_framebuffer = fb;
}
uintptr_t coreGetProcAddress_cgo(const char *sym) {
uintptr_t coreGetProcAddress(const char *sym);
return coreGetProcAddress(sym);
}
*/
import "C"
+107 -18
View File
@@ -36,6 +36,8 @@ void bridge_retro_unload_game(void *f);
void bridge_retro_run(void *f);
void bridge_retro_reset(void *f);
void bridge_retro_frame_time_callback(retro_frame_time_callback_t f, retro_usec_t usec);
void bridge_retro_hw_context_reset(retro_hw_context_reset_t f);
void bridge_retro_hw_context_destroy(retro_hw_context_reset_t f);
void bridge_retro_audio_callback(retro_audio_callback_t f);
void bridge_retro_audio_set_state(retro_audio_set_state_callback_t f, bool state);
size_t bridge_retro_get_memory_size(void *f, unsigned id);
@@ -54,6 +56,9 @@ size_t coreAudioSampleBatch_cgo(const int16_t *data, size_t frames);
int16_t coreInputState_cgo(unsigned port, unsigned device, unsigned index, unsigned id);
void coreLog_cgo(enum retro_log_level level, const char *msg);
int64_t coreGetTimeUsec_cgo();
uintptr_t coreGetCurrentFramebuffer_cgo();
void coreSetCurrentFramebuffer_cgo(uintptr_t fb);
uintptr_t coreGetProcAddress_cgo(const char *sym);
*/
import "C"
import (
@@ -219,6 +224,20 @@ type FrameTimeCallback struct {
Reference int64
}
// HWRenderCallback sets an interface to let a libretro core render with
// hardware acceleration.
type HWRenderCallback struct {
HWContextType uint32
ContextReset func()
Depth bool
Stencil bool
BottomLeftOrigin bool
VersionMajor, VersionMinor uint
CacheContext bool
ContextDestroy func()
DebugContext bool
}
// AudioCallback stores the audio callback itself and the SetState callback
type AudioCallback struct {
Callback func()
@@ -412,26 +431,48 @@ const (
MemoryVideoRAM = uint32(C.RETRO_MEMORY_VIDEO_RAM)
)
// Hardware contexts
const (
HWContextNone = uint32(C.RETRO_HW_CONTEXT_NONE)
HWContextOpenGL = uint32(C.RETRO_HW_CONTEXT_OPENGL)
HWContextOpenGLES2 = uint32(C.RETRO_HW_CONTEXT_OPENGLES2)
HWContextOpenGLCore = uint32(C.RETRO_HW_CONTEXT_OPENGL_CORE)
HWContextOpenGLES3 = uint32(C.RETRO_HW_CONTEXT_OPENGLES3)
HWContextOpenGLESVersion = uint32(C.RETRO_HW_CONTEXT_OPENGLES_VERSION)
HWContextVulkan = uint32(C.RETRO_HW_CONTEXT_VULKAN)
HWContextDummy = uint32(C.RETRO_HW_CONTEXT_DUMMY)
)
// Pass this to retro_video_refresh_t if rendering to hardware.
// Passing NULL to retro_video_refresh_t is still a frame dupe as normal.
var (
HWFrameBufferValid = unsafe.Pointer(C.RETRO_HW_FRAME_BUFFER_VALID)
)
type (
environmentFunc func(uint32, unsafe.Pointer) bool
videoRefreshFunc func(unsafe.Pointer, int32, int32, int32)
audioSampleFunc func(int16, int16)
audioSampleBatchFunc func([]byte, int32) int32
inputPollFunc func()
inputStateFunc func(uint, uint32, uint, uint) int16
logFunc func(uint32, string)
getTimeUsecFunc func() int64
environmentFunc func(uint32, unsafe.Pointer) bool
videoRefreshFunc func(unsafe.Pointer, int32, int32, int32)
audioSampleFunc func(int16, int16)
audioSampleBatchFunc func([]byte, int32) int32
inputPollFunc func()
inputStateFunc func(uint, uint32, uint, uint) int16
logFunc func(uint32, string)
getTimeUsecFunc func() int64
getCurrentFramebufferFunc func() uintptr
getProcAddressFunc func(string) uintptr
)
var (
environment environmentFunc
videoRefresh videoRefreshFunc
audioSample audioSampleFunc
audioSampleBatch audioSampleBatchFunc
inputPoll inputPollFunc
inputState inputStateFunc
log logFunc
getTimeUsec getTimeUsecFunc
environment environmentFunc
videoRefresh videoRefreshFunc
audioSample audioSampleFunc
audioSampleBatch audioSampleBatchFunc
inputPoll inputPollFunc
inputState inputStateFunc
log logFunc
getTimeUsec getTimeUsecFunc
getCurrentFramebuffer getCurrentFramebufferFunc
getProcAddress getProcAddressFunc
)
// Load dynamically loads a libretro core at the given path and returns a Core instance
@@ -444,8 +485,6 @@ func Load(sofile string) (*Core, error) {
return nil, err
}
C.cothread_init()
core.symRetroInit = DlSym(core.handle, "retro_init")
core.symRetroDeinit = DlSym(core.handle, "retro_deinit")
core.symRetroAPIVersion = DlSym(core.handle, "retro_api_version")
@@ -495,6 +534,8 @@ func (core *Core) Deinit() {
inputState = nil
log = nil
getTimeUsec = nil
getCurrentFramebuffer = nil
getProcAddress = nil
}
// Run runs the game for one video frame.
@@ -726,6 +767,21 @@ 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)))
}
// SetCurrentFramebufferValue updates the C-side framebuffer callback target
func SetCurrentFramebufferValue(fb uintptr) {
C.coreSetCurrentFramebuffer_cgo(C.uintptr_t(fb))
}
// SetData is a setter for the data of a GameInfo type
func (gi *GameInfo) SetData(bytes []byte) {
cstr := C.CString(string(bytes))
@@ -888,6 +944,39 @@ func (core *Core) SetFrameTimeCallback(data unsafe.Pointer) {
core.FrameTimeCallback = ftc
}
// SetHWRenderCallback is an environment callback helper to set the HWRenderCallback
func SetHWRenderCallback(
data unsafe.Pointer,
currentFramebuffer getCurrentFramebufferFunc,
procAddress getProcAddressFunc,
) *HWRenderCallback {
c := (*C.struct_retro_hw_render_callback)(data)
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)
c.get_proc_address = (C.retro_hw_get_proc_address_t)(C.coreGetProcAddress_cgo)
hwrc.ContextReset = func() {
C.bridge_retro_hw_context_reset((C.retro_hw_context_reset_t)(c.context_reset))
}
hwrc.Depth = bool(c.depth)
hwrc.Stencil = bool(c.stencil)
hwrc.BottomLeftOrigin = bool(c.bottom_left_origin)
hwrc.VersionMajor = uint(c.version_major)
hwrc.VersionMinor = uint(c.version_minor)
hwrc.CacheContext = bool(c.cache_context)
hwrc.DebugContext = bool(c.debug_context)
hwrc.ContextDestroy = func() {
C.bridge_retro_hw_context_destroy((C.retro_hw_context_reset_t)(c.context_destroy))
}
return &hwrc
}
// SetAudioCallback is an environment callback helper to set the AudioCallback
func (core *Core) SetAudioCallback(data unsafe.Pointer) {
c := *(*C.struct_retro_audio_callback)(data)
+1
View File
@@ -31,6 +31,7 @@ type Core struct {
AudioCallback *AudioCallback
FrameTimeCallback *FrameTimeCallback
DiskControlCallback *DiskControlCallback
HWRenderCallback *HWRenderCallback
MemoryMap []MemoryDescriptor
}
+2
View File
@@ -44,7 +44,9 @@ func runLoop(vid *video.Video, m *menu.Menu) {
input.Poll()
if !state.MenuActive {
if state.CoreRunning {
vid.BeginCoreFrame()
state.Core.Run()
vid.EndCoreFrame()
if state.Core.FrameTimeCallback != nil {
state.Core.FrameTimeCallback.Callback(state.Core.FrameTimeCallback.Reference)
}
+2
View File
@@ -33,3 +33,5 @@ var LudOS bool
// FastForward will run the core as fast as possible
var FastForward bool
var CoreSetSharedContext bool
+3 -1
View File
@@ -19,8 +19,10 @@ COMPAT_ATTRIBUTE vec2 vertTexCoord;
COMPAT_VARYING vec2 fragTexCoord;
uniform mat4 MVP;
void main() {
fragTexCoord = vertTexCoord;
gl_Position = vec4(vert, 0.0, 1.0);
gl_Position = vec4(vert, 0.0, 1.0) * MVP;
}
` + "\x00"
+28
View File
@@ -7,6 +7,23 @@ import (
"github.com/go-gl/gl/v2.1/gl"
)
func identityMatrix() [16]float32 {
return [16]float32{
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1,
}
}
func orthoMatrix(bottomLeftOrigin bool) [16]float32 {
m := identityMatrix()
if bottomLeftOrigin {
m[5] = -1
}
return m
}
func newProgram(vertexShaderSource, fragmentShaderSource string) (uint32, error) {
vertexShader, err := compileShader(vertexShaderSource, gl.VERTEX_SHADER)
if err != nil {
@@ -22,6 +39,8 @@ func newProgram(vertexShaderSource, fragmentShaderSource string) (uint32, error)
gl.AttachShader(program, vertexShader)
gl.AttachShader(program, fragmentShader)
gl.BindAttribLocation(program, 0, gl.Str("vert\x00"))
gl.BindAttribLocation(program, 1, gl.Str("vertTexCoord\x00"))
gl.LinkProgram(program)
var status int32
@@ -39,6 +58,15 @@ func newProgram(vertexShaderSource, fragmentShaderSource string) (uint32, error)
gl.DeleteShader(vertexShader)
gl.DeleteShader(fragmentShader)
gl.UseProgram(program)
mvp := gl.GetUniformLocation(program, gl.Str("MVP\x00"))
if mvp != -1 {
identity := identityMatrix()
gl.UniformMatrix4fv(mvp, 1, false, &identity[0])
}
gl.UseProgram(0)
return program, nil
}
+558 -113
View File
@@ -17,11 +17,13 @@ import (
// Video holds the state of the video package
type Video struct {
Window *glfw.Window
Geom libretro.GameGeometry
Font *Font
FontSm *Font
FontLg *Font
Window *glfw.Window
HWWindow *glfw.Window
Geom libretro.GameGeometry
Font *Font
FontSm *Font
FontLg *Font
title string
program uint32 // current program used for the game quad
defaultProgram uint32 // default program used for the game quad
@@ -34,6 +36,12 @@ type Video struct {
vao uint32 // vertex array object
vbo uint32 // vertex buffer object
texID uint32 // texture id
fboID uint32
rboID uint32
identityMat [16]float32
orthoMat [16]float32
texWidth int32
texHeight int32
pitch int32 // pitch set by the refresh callback
pixFmt uint32 // format set by the environment callback
@@ -42,13 +50,26 @@ type Video struct {
width, height int32 // dimensions set by the refresh callback
rot uint // rotation index
needUpload bool // true when the texture needs to be uploaded to the GPU
data unsafe.Pointer
hwContextType uint32
hwContextMajor int
hwContextMinor int
hwDebugContext bool
hwContextConfigured bool
presentDirty bool
presentFBWidth int
presentFBHeight int
presentClipWidth int32
presentClipHeight int32
presentRot uint
}
// Init instanciates the video package
func Init(fullscreen bool) *Video {
vid := &Video{}
vid := &Video{
identityMat: identityMatrix(),
orthoMat: identityMatrix(),
title: "Ludo",
}
vid.Configure(fullscreen)
return vid
}
@@ -56,8 +77,13 @@ func Init(fullscreen bool) *Video {
// Reconfigure destroys and recreates the window with new attributes
func (video *Video) Reconfigure(fullscreen bool) {
if video.Window != nil {
if video.HWWindow != nil {
video.HWWindow.Destroy()
video.HWWindow = nil
}
video.Window.Destroy()
}
video.resetFrontendState()
video.Configure(fullscreen)
}
@@ -71,6 +97,7 @@ func (video *Video) GetFramebufferSize() (int, int) {
// SetTitle sets the window title, encoded as UTF-8, of the window.
func (video *Video) SetTitle(title string) {
video.title = title
if video.Window == nil {
return
}
@@ -92,42 +119,351 @@ func panicOnErr(v uint32, err error) uint32 {
return v
}
// Configure instanciates the video package
func (video *Video) Configure(fullscreen bool) {
var width, height int
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
} else {
width = 384 * 2
height = 240 * 2
// SetHWRenderContext stores the context requested by the current libretro core
func (video *Video) SetHWRenderContext(hw *libretro.HWRenderCallback) {
if hw == nil {
video.hwContextType = 0
video.hwContextMajor = 0
video.hwContextMinor = 0
video.hwDebugContext = false
video.hwContextConfigured = false
return
}
var err error
video.Window, err = glfw.CreateWindow(width, height, "Ludo", m, nil)
video.hwContextType = hw.HWContextType
video.hwContextMajor = int(hw.VersionMajor)
video.hwContextMinor = int(hw.VersionMinor)
video.hwDebugContext = hw.DebugContext
video.hwContextConfigured = true
}
func (video *Video) configureWindowHints() {
glfw.DefaultWindowHints()
if !video.hwContextConfigured {
return
}
switch video.hwContextType {
case libretro.HWContextOpenGLCore:
major := video.hwContextMajor
minor := video.hwContextMinor
glfw.WindowHint(glfw.ClientAPI, glfw.OpenGLAPI)
glfw.WindowHint(glfw.OpenGLProfile, glfw.OpenGLAnyProfile)
if major > 0 && !(major == 3 && minor == 1) {
glfw.WindowHint(glfw.ContextVersionMajor, major)
glfw.WindowHint(glfw.ContextVersionMinor, minor)
if major > 3 || (major == 3 && minor >= 2) {
glfw.WindowHint(glfw.OpenGLProfile, glfw.OpenGLCoreProfile)
}
}
case libretro.HWContextOpenGL:
glfw.WindowHint(glfw.ClientAPI, glfw.OpenGLAPI)
if video.hwContextMajor > 0 {
glfw.WindowHint(glfw.ContextVersionMajor, video.hwContextMajor)
glfw.WindowHint(glfw.ContextVersionMinor, video.hwContextMinor)
if video.hwContextMajor > 3 || (video.hwContextMajor == 3 && video.hwContextMinor >= 2) {
glfw.WindowHint(glfw.OpenGLProfile, glfw.OpenGLCompatProfile)
}
}
case libretro.HWContextOpenGLES2:
glfw.WindowHint(glfw.ClientAPI, glfw.OpenGLESAPI)
glfw.WindowHint(glfw.ContextVersionMajor, 2)
glfw.WindowHint(glfw.ContextVersionMinor, 0)
case libretro.HWContextOpenGLES3:
glfw.WindowHint(glfw.ClientAPI, glfw.OpenGLESAPI)
glfw.WindowHint(glfw.ContextVersionMajor, 3)
glfw.WindowHint(glfw.ContextVersionMinor, 0)
case libretro.HWContextOpenGLESVersion:
glfw.WindowHint(glfw.ClientAPI, glfw.OpenGLESAPI)
if video.hwContextMajor > 0 {
glfw.WindowHint(glfw.ContextVersionMajor, video.hwContextMajor)
glfw.WindowHint(glfw.ContextVersionMinor, video.hwContextMinor)
}
}
if video.hwDebugContext {
glfw.WindowHint(glfw.OpenGLDebugContext, glfw.True)
}
}
func (video *Video) textureSize() (int32, int32) {
if video.texWidth > 0 && video.texHeight > 0 {
return video.texWidth, video.texHeight
}
w := int32(video.Geom.MaxWidth)
h := int32(video.Geom.MaxHeight)
if w == 0 {
if video.width > 0 {
w = video.width
} else {
w = int32(video.Geom.BaseWidth)
}
}
if h == 0 {
if video.height > 0 {
h = video.height
} else {
h = int32(video.Geom.BaseHeight)
}
}
if w < 1 {
w = 1
}
if h < 1 {
h = 1
}
return w, h
}
func (video *Video) resetFrontendState() {
video.Window = nil
video.HWWindow = nil
video.Font = nil
video.FontSm = nil
video.FontLg = nil
video.program = 0
video.defaultProgram = 0
video.sharpBilinearProgram = 0
video.zfastCRTProgram = 0
video.zfastLCDProgram = 0
video.roundedProgram = 0
video.borderProgram = 0
video.circleProgram = 0
video.vao = 0
video.vbo = 0
video.texID = 0
video.fboID = 0
video.rboID = 0
video.presentDirty = true
video.presentFBWidth = 0
video.presentFBHeight = 0
video.presentClipWidth = 0
video.presentClipHeight = 0
video.presentRot = 0
libretro.SetCurrentFramebufferValue(0)
}
func (video *Video) createSharedHWContext() error {
if video.Window == nil || state.Core == nil || state.Core.HWRenderCallback == nil || state.CoreRunning || !state.CoreSetSharedContext {
return nil
}
video.configureWindowHints()
glfw.WindowHint(glfw.Visible, glfw.False)
glfw.WindowHint(glfw.Focused, glfw.False)
hwWindow, err := glfw.CreateWindow(1, 1, "", nil, video.Window)
if err != nil {
panic("Window creation failed:" + err.Error())
return err
}
video.Window.MakeContextCurrent()
video.HWWindow = hwWindow
video.MakeFrontendContextCurrent()
return nil
}
// Force a minimum size for the window.
video.Window.SetSizeLimits(160, 120, glfw.DontCare, glfw.DontCare)
video.Window.SetInputMode(glfw.CursorMode, glfw.CursorHidden)
// Initialize Glow
if err := gl.Init(); err != nil {
panic(err)
// MakeFrontendContextCurrent switches back to the visible window context
func (video *Video) MakeFrontendContextCurrent() {
if video.HWWindow != nil && video.Window != nil {
video.Window.MakeContextCurrent()
}
}
// MakeHardwareContextCurrent switches to the shared HW context when available
func (video *Video) MakeHardwareContextCurrent() {
if video.HWWindow != nil {
video.HWWindow.MakeContextCurrent()
}
}
// BeginCoreFrame makes the hardware context current before the core issues GL calls
func (video *Video) BeginCoreFrame() {
video.MakeHardwareContextCurrent()
}
// EndCoreFrame flushes HW GL work and restores the visible frontend context
func (video *Video) EndCoreFrame() {
if video.HWWindow != nil {
gl.Flush()
}
video.MakeFrontendContextCurrent()
}
// PrepareCoreContext resets frontend GL state so HW cores see a minimal context
func (video *Video) PrepareCoreContext() {
gl.UseProgram(0)
bindVertexArray(0)
gl.BindBuffer(gl.ARRAY_BUFFER, 0)
gl.BindBuffer(gl.ELEMENT_ARRAY_BUFFER, 0)
gl.ActiveTexture(gl.TEXTURE0)
gl.BindTexture(gl.TEXTURE_2D, 0)
gl.BindTexture(gl.TEXTURE_CUBE_MAP, 0)
gl.BindRenderbuffer(gl.RENDERBUFFER, 0)
bindBackbuffer()
gl.Disable(gl.BLEND)
gl.Disable(gl.CULL_FACE)
gl.Disable(gl.DEPTH_TEST)
gl.Disable(gl.DITHER)
gl.Disable(gl.SCISSOR_TEST)
gl.Disable(gl.STENCIL_TEST)
gl.PixelStorei(gl.UNPACK_ROW_LENGTH, 0)
gl.PixelStorei(gl.PACK_ROW_LENGTH, 0)
gl.PixelStorei(gl.UNPACK_ALIGNMENT, 4)
gl.PixelStorei(gl.PACK_ALIGNMENT, 4)
}
func (video *Video) invalidatePresentGeometry() {
video.presentDirty = true
}
func (video *Video) syncPresentGeometry(clipWidth, clipHeight int32) (float32, float32) {
fbw, fbh := video.Window.GetFramebufferSize()
if video.presentDirty ||
video.presentFBWidth != fbw ||
video.presentFBHeight != fbh ||
video.presentClipWidth != clipWidth ||
video.presentClipHeight != clipHeight ||
video.presentRot != video.rot {
_, _, w, h := video.coreRatioViewport(fbw, fbh, int(clipWidth), int(clipHeight))
video.presentFBWidth = fbw
video.presentFBHeight = fbh
video.presentClipWidth = clipWidth
video.presentClipHeight = clipHeight
video.presentRot = video.rot
video.presentDirty = false
return w, h
}
aspectRatio := float32(video.Geom.AspectRatio)
if aspectRatio == 0 && clipWidth > 0 && clipHeight > 0 {
aspectRatio = float32(clipWidth) / float32(clipHeight)
}
if aspectRatio == 0 {
aspectRatio = 1
}
w := float32(fbh) * aspectRatio
h := float32(fbh)
if w > float32(fbw) {
h = float32(fbw) / aspectRatio
w = float32(fbw)
}
return w, h
}
func (video *Video) presentFrame() {
if video.Window == nil || video.vao == 0 || video.program == 0 {
return
}
video.MakeFrontendContextCurrent()
bindBackbuffer()
fbw, fbh := video.Window.GetFramebufferSize()
gl.Viewport(0, 0, int32(fbw), int32(fbh))
gl.Disable(gl.DEPTH_TEST)
gl.Disable(gl.CULL_FACE)
gl.Disable(gl.DITHER)
gl.Disable(gl.STENCIL_TEST)
gl.Disable(gl.BLEND)
gl.ClearColor(0, 0, 0, 1)
gl.Clear(gl.COLOR_BUFFER_BIT)
w, h := video.syncPresentGeometry(video.width, video.height)
gl.UseProgram(video.program)
video.setGameProgramColor(video.program)
gl.Uniform2f(gl.GetUniformLocation(video.program, gl.Str("OutputSize\x00")), w, h)
gl.Uniform2f(gl.GetUniformLocation(video.program, gl.Str("TextureSize\x00")), float32(video.texWidth), float32(video.texHeight))
gl.Uniform2f(gl.GetUniformLocation(video.program, gl.Str("InputSize\x00")), float32(video.width), float32(video.height))
mvp := video.identityMat
if state.Core.HWRenderCallback != nil {
mvp = video.orthoMat
}
gl.UniformMatrix4fv(gl.GetUniformLocation(video.program, gl.Str("MVP\x00")), 1, false, &mvp[0])
gl.ActiveTexture(gl.TEXTURE0)
gl.BindTexture(gl.TEXTURE_2D, video.texID)
gl.BindBuffer(gl.ARRAY_BUFFER, video.vbo)
bindVertexArray(video.vao)
gl.DrawArrays(gl.TRIANGLE_STRIP, 0, 4)
bindVertexArray(0)
gl.UniformMatrix4fv(gl.GetUniformLocation(video.program, gl.Str("MVP\x00")), 1, false, &video.identityMat[0])
gl.UseProgram(0)
}
func (video *Video) initSharedTexture() {
gl.GenTextures(1, &video.texID)
if video.texID == 0 && state.Verbose {
log.Println("[Video]: Failed to create the vid texture")
}
if video.pixFmt == 0 {
// Some cores won't call SetPixelFormat, provide default values
video.pixFmt = gl.UNSIGNED_SHORT_5_5_5_1
video.pixType = gl.BGRA
video.bpp = 2
}
gl.BindTexture(gl.TEXTURE_2D, video.texID)
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST)
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST)
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE)
gl.BindTexture(gl.TEXTURE_2D, 0)
}
func (video *Video) InitPresentResources() {
if video.Window == nil || video.defaultProgram != 0 {
return
}
video.defaultProgram = panicOnErr(newProgram(vertexShader, defaultFragmentShader))
if video.sharpBilinearProgram == 0 {
video.sharpBilinearProgram = panicOnErr(newProgram(vertexShader, sharpBilinearFragmentShader))
}
if video.zfastCRTProgram == 0 {
video.zfastCRTProgram = panicOnErr(newProgram(vertexShader, zfastCRTFragmentShader))
}
if video.zfastLCDProgram == 0 {
video.zfastLCDProgram = panicOnErr(newProgram(vertexShader, zfastLCDFragmentShader))
}
video.program = video.defaultProgram
genVertexArrays(1, &video.vao)
bindVertexArray(video.vao)
gl.GenBuffers(1, &video.vbo)
gl.BindBuffer(gl.ARRAY_BUFFER, video.vbo)
gl.BufferData(gl.ARRAY_BUFFER, len(vertices)*4, gl.Ptr(vertices), gl.STATIC_DRAW)
gl.EnableVertexAttribArray(0)
gl.VertexAttribPointerWithOffset(0, 2, gl.FLOAT, false, 4*4, 0)
gl.EnableVertexAttribArray(1)
gl.VertexAttribPointerWithOffset(1, 2, gl.FLOAT, false, 4*4, 2*4)
fbw, fbh := video.Window.GetFramebufferSize()
video.coreRatioViewport(fbw, fbh, video.Geom.BaseWidth, video.Geom.BaseHeight)
bindVertexArray(0)
gl.BindBuffer(gl.ARRAY_BUFFER, 0)
gl.UseProgram(0)
for e := gl.GetError(); e != gl.NO_ERROR; e = gl.GetError() {
log.Printf("[Video] OpenGL error: %d\n", e)
}
video.UpdateFilter(settings.Current.VideoFilter)
}
func (video *Video) InitUIResources() {
if video.Window == nil {
return
}
video.InitPresentResources()
var err error
fbw, fbh := video.Window.GetFramebufferSize()
// LoadFont (fontfile, font scale, window width, window height)
fontPath := filepath.Join(settings.Current.AssetsDirectory, "font.ttf")
video.Font, err = LoadFont(fontPath, int32(36), fbw, fbh)
@@ -143,59 +479,51 @@ func (video *Video) Configure(fullscreen bool) {
panic(err)
}
// Configure the vertex and fragment shader
video.defaultProgram = panicOnErr(newProgram(vertexShader, defaultFragmentShader))
video.sharpBilinearProgram = panicOnErr(newProgram(vertexShader, sharpBilinearFragmentShader))
video.zfastCRTProgram = panicOnErr(newProgram(vertexShader, zfastCRTFragmentShader))
video.zfastLCDProgram = panicOnErr(newProgram(vertexShader, zfastLCDFragmentShader))
video.roundedProgram = panicOnErr(newProgram(vertexShader, roundedFragmentShader))
video.borderProgram = panicOnErr(newProgram(vertexShader, borderFragmentShader))
video.circleProgram = panicOnErr(newProgram(vertexShader, circleFragmentShader))
}
video.UpdateFilter(settings.Current.VideoFilter)
// Configure instanciates the video package
func (video *Video) Configure(fullscreen bool) {
var width, height int
var monitor *glfw.Monitor
textureUniform := gl.GetUniformLocation(video.program, gl.Str("Texture\x00"))
gl.Uniform1i(textureUniform, 0)
// Configure the vertex data
genVertexArrays(1, &video.vao)
bindVertexArray(video.vao)
gl.GenBuffers(1, &video.vbo)
gl.BindBuffer(gl.ARRAY_BUFFER, video.vbo)
gl.BufferData(gl.ARRAY_BUFFER, len(vertices)*4, gl.Ptr(vertices), gl.STATIC_DRAW)
vertAttrib := uint32(gl.GetAttribLocation(video.program, gl.Str("vert\x00")))
gl.EnableVertexAttribArray(vertAttrib)
gl.VertexAttribPointerWithOffset(vertAttrib, 2, gl.FLOAT, false, 4*4, 0)
texCoordAttrib := uint32(gl.GetAttribLocation(video.program, gl.Str("vertTexCoord\x00")))
gl.EnableVertexAttribArray(texCoordAttrib)
gl.VertexAttribPointerWithOffset(texCoordAttrib, 2, gl.FLOAT, false, 4*4, 2*4)
// Some cores won't call SetPixelFormat, provide default values
if video.pixFmt == 0 {
video.pixFmt = gl.UNSIGNED_SHORT_5_5_5_1
video.pixType = gl.BGRA
video.bpp = 2
if fullscreen {
monitor = glfw.GetMonitors()[settings.Current.VideoMonitorIndex]
vms := monitor.GetVideoModes()
vm := vms[len(vms)-1]
width = vm.Width
height = vm.Height
} else {
width = 384 * 2
height = 240 * 2
}
gl.GenTextures(1, &video.texID)
video.configureWindowHints()
gl.ActiveTexture(gl.TEXTURE0)
if video.texID == 0 && state.Verbose {
log.Println("[Video]: Failed to create the vid texture")
var err error
video.Window, err = glfw.CreateWindow(width, height, video.title, monitor, nil)
if err != nil {
panic("Window creation failed:" + err.Error())
}
gl.BindTexture(gl.TEXTURE_2D, video.texID)
video.Window.MakeContextCurrent()
// Force a minimum size for the window.
video.Window.SetSizeLimits(160, 120, glfw.DontCare, glfw.DontCare)
video.Window.SetInputMode(glfw.CursorMode, glfw.CursorHidden)
video.UpdateFilter(settings.Current.VideoFilter)
video.coreRatioViewport(fbw, fbh)
if e := gl.GetError(); e != gl.NO_ERROR {
log.Printf("[Video] OpenGL error: %d\n", e)
// Initialize Glow
if err := gl.Init(); err != nil {
panic(err)
}
video.initSharedTexture()
if err := video.createSharedHWContext(); err != nil {
panic("Shared HW context creation failed:" + err.Error())
}
video.InitPresentResources()
video.InitUIResources()
}
// UpdateFilter configures the game texture filter and shader. We currently
@@ -206,6 +534,7 @@ func (video *Video) Configure(fullscreen bool) {
// CRT: zfast-crt
// LCD: zfast-lcd
func (video *Video) UpdateFilter(filter string) {
gl.ActiveTexture(gl.TEXTURE0)
gl.BindTexture(gl.TEXTURE_2D, video.texID)
switch filter {
case "Smooth":
@@ -215,15 +544,27 @@ func (video *Video) UpdateFilter(filter string) {
case "Pixel Perfect":
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR)
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
video.program = video.sharpBilinearProgram
if video.sharpBilinearProgram != 0 {
video.program = video.sharpBilinearProgram
} else {
video.program = video.defaultProgram
}
case "CRT":
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR)
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
video.program = video.zfastCRTProgram
if video.zfastCRTProgram != 0 {
video.program = video.zfastCRTProgram
} else {
video.program = video.defaultProgram
}
case "LCD":
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR)
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
video.program = video.zfastLCDProgram
if video.zfastLCDProgram != 0 {
video.program = video.zfastLCDProgram
} else {
video.program = video.defaultProgram
}
case "Raw":
fallthrough
default:
@@ -233,9 +574,21 @@ func (video *Video) UpdateFilter(filter string) {
}
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE)
gl.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE)
textureWidth, textureHeight := video.textureSize()
gl.UseProgram(video.program)
gl.Uniform2f(gl.GetUniformLocation(video.program, gl.Str("TextureSize\x00")), float32(video.width), float32(video.height))
gl.Uniform1i(gl.GetUniformLocation(video.program, gl.Str("Texture\x00")), 0)
gl.Uniform2f(gl.GetUniformLocation(video.program, gl.Str("TextureSize\x00")), float32(textureWidth), float32(textureHeight))
gl.Uniform2f(gl.GetUniformLocation(video.program, gl.Str("InputSize\x00")), float32(video.width), float32(video.height))
gl.UseProgram(0)
video.invalidatePresentGeometry()
}
func (video *Video) setGameProgramColor(program uint32) {
loc := gl.GetUniformLocation(program, gl.Str("color\x00"))
if loc != -1 {
gl.Uniform4f(loc, 1, 1, 1, 1)
}
}
// SetPixelFormat is a callback passed to the libretro implementation.
@@ -245,24 +598,24 @@ func (video *Video) SetPixelFormat(format uint32) bool {
log.Printf("[Video]: Set Pixel Format: %v\n", format)
}
// PixelStorei also needs to be updated whenever bpp changes
defer func() { video.needUpload = true }()
switch format {
case libretro.PixelFormat0RGB1555:
video.pixFmt = gl.UNSIGNED_SHORT_5_5_5_1
video.pixType = gl.BGRA
video.bpp = 2
video.invalidatePresentGeometry()
return true
case libretro.PixelFormatXRGB8888:
video.pixFmt = gl.UNSIGNED_INT_8_8_8_8_REV
video.pixType = gl.BGRA
video.bpp = 4
video.invalidatePresentGeometry()
return true
case libretro.PixelFormatRGB565:
video.pixFmt = gl.UNSIGNED_SHORT_5_6_5
video.pixType = gl.RGB
video.bpp = 2
video.invalidatePresentGeometry()
return true
default:
log.Printf("Unknown pixel type %v", format)
@@ -281,19 +634,30 @@ func (video *Video) ResetPitch() {
// be rendered with the wrong rotation
func (video *Video) ResetRot() {
video.rot = 0
video.invalidatePresentGeometry()
}
// 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 int, fbHeight int) (x, y, w, h float32) {
// Scale the content to fit in the viewport.
func (video *Video) coreRatioViewport(fbWidth, fbHeight, clipWidth, clipHeight int) (x, y, w, h float32) {
fbw := float32(fbWidth)
fbh := float32(fbHeight)
// NXEngine workaround
aspectRatio := float32(video.Geom.AspectRatio)
if aspectRatio == 0 {
aspectRatio = float32(video.Geom.BaseWidth) / float32(video.Geom.BaseHeight)
baseWidth := video.Geom.BaseWidth
baseHeight := video.Geom.BaseHeight
if baseWidth == 0 {
baseWidth = clipWidth
}
if baseHeight == 0 {
baseHeight = clipHeight
}
if baseWidth > 0 && baseHeight > 0 {
aspectRatio = float32(baseWidth) / float32(baseHeight)
} else {
aspectRatio = 1
}
}
h = fbh
@@ -307,7 +671,20 @@ func (video *Video) coreRatioViewport(fbWidth int, fbHeight int) (x, y, w, h flo
x = (fbw - w) / 2
y = (fbh - h) / 2
textureWidth, textureHeight := video.textureSize()
if clipWidth == 0 {
clipWidth = int(textureWidth)
}
if clipHeight == 0 {
clipHeight = int(textureHeight)
}
va := video.vertexArray(x, y, w, h, 1.0)
va[3] = float32(clipHeight) / float32(textureHeight)
va[10] = float32(clipWidth) / float32(textureWidth)
va[11] = va[3]
va[14] = va[10]
va = rotateUV(va, video.rot)
gl.BindBuffer(gl.ARRAY_BUFFER, video.vbo)
gl.BufferData(gl.ARRAY_BUFFER, len(va)*4, gl.Ptr(va), gl.STATIC_DRAW)
@@ -317,8 +694,12 @@ func (video *Video) coreRatioViewport(fbWidth int, fbHeight int) (x, y, w, h flo
// ResizeViewport resizes the GL viewport to the framebuffer size
func (video *Video) ResizeViewport() {
video.MakeFrontendContextCurrent()
fbw, fbh := video.Window.GetFramebufferSize()
gl.Viewport(0, 0, int32(fbw), int32(fbh))
if video.presentFBWidth != fbw || video.presentFBHeight != fbh {
video.invalidatePresentGeometry()
}
if video.Font != nil {
video.Font.UpdateResolution(fbw, fbh)
}
@@ -332,6 +713,20 @@ func (video *Video) ResizeViewport() {
// Render the current frame
func (video *Video) Render() {
video.MakeFrontendContextCurrent()
bindBackbuffer()
// We can't trust the core to leave OpenGL state untouched after retro_run().
gl.Disable(gl.DEPTH_TEST)
gl.Disable(gl.CULL_FACE)
gl.Disable(gl.DITHER)
gl.Disable(gl.STENCIL_TEST)
gl.Disable(gl.BLEND)
gl.BlendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA)
gl.BlendEquation(gl.FUNC_ADD)
video.ResizeViewport()
if !state.CoreRunning {
gl.ClearColor(1, 1, 1, 1)
gl.Clear(gl.COLOR_BUFFER_BIT)
@@ -342,49 +737,99 @@ func (video *Video) Render() {
// Early return to not render the first frame of a newly loaded game with the
// previous game pitch. A sane pitch must be set by video.Refresh first.
if video.pitch == 0 {
if state.Core.HWRenderCallback == nil && video.pitch == 0 {
return
}
video.uploadTexture()
if state.Core.HWRenderCallback != nil {
video.presentFrame()
return
}
fbw, fbh := video.Window.GetFramebufferSize()
_, _, w, h := video.coreRatioViewport(fbw, fbh)
_, _, w, h := video.coreRatioViewport(fbw, fbh, int(video.width), int(video.height))
gl.UseProgram(video.program)
video.setGameProgramColor(video.program)
gl.Uniform2f(gl.GetUniformLocation(video.program, gl.Str("OutputSize\x00")), w, h)
bindVertexArray(video.vao)
gl.BindTexture(gl.TEXTURE_2D, video.texID)
gl.BindBuffer(gl.ARRAY_BUFFER, video.vbo)
gl.DrawArrays(gl.TRIANGLE_STRIP, 0, 4)
}
// Refresh the texture framebuffer
func (video *Video) Refresh(data unsafe.Pointer, width int32, height int32, pitch int32) {
video.needUpload = true
video.width = width
video.height = height
video.pitch = pitch
video.data = data // maybe need a full copy
}
func (video *Video) uploadTexture() {
if !video.needUpload || video.data == nil {
return
if state.Core.HWRenderCallback != nil {
gl.UniformMatrix4fv(gl.GetUniformLocation(video.program, gl.Str("MVP\x00")), 1, false, &video.orthoMat[0])
}
gl.ActiveTexture(gl.TEXTURE0)
gl.BindTexture(gl.TEXTURE_2D, video.texID)
gl.PixelStorei(gl.UNPACK_ROW_LENGTH, video.pitch/video.bpp)
gl.BindBuffer(gl.ARRAY_BUFFER, video.vbo)
bindVertexArray(video.vao)
gl.DrawArrays(gl.TRIANGLE_STRIP, 0, 4)
bindVertexArray(0)
gl.UniformMatrix4fv(gl.GetUniformLocation(video.program, gl.Str("MVP\x00")), 1, false, &video.identityMat[0])
gl.UseProgram(0)
}
// Refresh the texture framebuffer
func (video *Video) Refresh(data unsafe.Pointer, width int32, height int32, pitch int32) {
bindBackbuffer()
if video.width != width || video.height != height {
video.invalidatePresentGeometry()
}
video.width = width
video.height = height
video.pitch = pitch
textureWidth, textureHeight := video.textureSize()
gl.ActiveTexture(gl.TEXTURE0)
gl.BindTexture(gl.TEXTURE_2D, video.texID)
if video.texWidth != textureWidth || video.texHeight != textureHeight {
gl.TexImage2D(gl.TEXTURE_2D, 0, gl.RGBA8, textureWidth, textureHeight, 0, video.pixType, video.pixFmt, nil)
video.texWidth = textureWidth
video.texHeight = textureHeight
video.invalidatePresentGeometry()
}
if video.bpp > 0 {
// PixelStorei also needs to be updated whenever bpp changes
gl.PixelStorei(gl.UNPACK_ROW_LENGTH, video.pitch/video.bpp)
}
if data != nil && data != libretro.HWFrameBufferValid {
gl.TexSubImage2D(gl.TEXTURE_2D, 0, 0, 0, width, height, video.pixType, video.pixFmt, data)
}
if video.bpp > 0 {
gl.PixelStorei(gl.UNPACK_ROW_LENGTH, 0)
}
gl.UseProgram(video.program)
gl.Uniform2f(gl.GetUniformLocation(video.program, gl.Str("TextureSize\x00")), float32(video.width), float32(video.height))
gl.Uniform2f(gl.GetUniformLocation(video.program, gl.Str("InputSize\x00")), float32(video.width), float32(video.height))
gl.Uniform2f(gl.GetUniformLocation(video.program, gl.Str("TextureSize\x00")), float32(textureWidth), float32(textureHeight))
gl.Uniform2f(gl.GetUniformLocation(video.program, gl.Str("InputSize\x00")), float32(width), float32(height))
gl.UseProgram(0)
gl.TexImage2D(gl.TEXTURE_2D, 0, gl.RGBA8, video.width, video.height, 0, video.pixType, video.pixFmt, video.data)
if state.Core.HWRenderCallback != nil && video.HWWindow == nil && !state.MenuActive {
video.presentFrame()
}
}
// CurrentFramebuffer returns the current FBO ID
func (video *Video) CurrentFramebuffer() uintptr {
return uintptr(video.fboID)
}
// ProcAddress returns the address of the proc from GLFW
func (video *Video) ProcAddress(procName string) uintptr {
if glfw.GetCurrentContext() == nil {
return 0
}
addr := glfw.GetProcAddress(procName)
if addr == nil {
return 0
}
return uintptr(addr)
}
// SetRotation rotates the game image as requested by the core
+57
View File
@@ -3,9 +3,66 @@
package video
import (
"log"
"github.com/go-gl/gl/v2.1/gl"
"github.com/libretro/ludo/libretro"
"github.com/libretro/ludo/state"
)
// InitFramebuffer initializes and configures the video frame buffer based on
// information 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_COMPONENT24
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)
}
}
video.orthoMat = orthoMatrix(hw.BottomLeftOrigin)
if st := gl.CheckFramebufferStatusEXT(gl.FRAMEBUFFER_EXT); st != gl.FRAMEBUFFER_COMPLETE_EXT {
log.Fatalf("[Video] Framebuffer is not complete. Error: %v\n", st)
}
bindBackbuffer()
gl.BindTexture(gl.TEXTURE_2D, 0)
gl.ClearColor(0, 0, 0, 1)
gl.Clear(gl.COLOR_BUFFER_BIT)
}
func bindBackbuffer() {
gl.BindFramebufferEXT(gl.FRAMEBUFFER_EXT, 0)
}
func genVertexArrays(n int32, arrays *uint32) {
gl.GenVertexArraysAPPLE(n, arrays)
}
+58
View File
@@ -3,9 +3,67 @@
package video
import (
"log"
"github.com/go-gl/gl/v2.1/gl"
"github.com/libretro/ludo/libretro"
"github.com/libretro/ludo/state"
)
// InitFramebuffer initializes and configures the video frame buffer based on
// information 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.GenFramebuffers(1, &video.fboID)
gl.BindFramebuffer(gl.FRAMEBUFFER, video.fboID)
libretro.SetCurrentFramebufferValue(uintptr(video.fboID))
gl.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)
format := gl.DEPTH_COMPONENT24
if hw.Stencil {
format = gl.DEPTH24_STENCIL8
}
gl.RenderbufferStorage(gl.RENDERBUFFER, uint32(format), width, height)
gl.BindRenderbuffer(gl.RENDERBUFFER, 0)
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)
}
}
video.orthoMat = orthoMatrix(hw.BottomLeftOrigin)
if st := gl.CheckFramebufferStatus(gl.FRAMEBUFFER); st != gl.FRAMEBUFFER_COMPLETE {
log.Fatalf("[Video] Framebuffer is not complete. Error: %v\n", st)
}
bindBackbuffer()
gl.BindTexture(gl.TEXTURE_2D, 0)
gl.ClearColor(0, 0, 0, 1)
gl.Clear(gl.COLOR_BUFFER_BIT)
}
func bindBackbuffer() {
gl.BindFramebuffer(gl.FRAMEBUFFER, 0)
}
func genVertexArrays(n int32, arrays *uint32) {
gl.GenVertexArrays(n, arrays)
}