mirror of
https://github.com/libretro/ludo.git
synced 2026-11-09 11:21:29 -10:00
Compare commits
63
Commits
master
...
kivutar/gl2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5798e14105 | ||
|
|
99f215025b | ||
|
|
1bd4ad6986 | ||
|
|
4795525132 | ||
|
|
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
+21
-1
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
@@ -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)
|
||||
|
||||
@@ -31,6 +31,7 @@ type Core struct {
|
||||
AudioCallback *AudioCallback
|
||||
FrameTimeCallback *FrameTimeCallback
|
||||
DiskControlCallback *DiskControlCallback
|
||||
HWRenderCallback *HWRenderCallback
|
||||
|
||||
MemoryMap []MemoryDescriptor
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -33,3 +33,5 @@ var LudOS bool
|
||||
|
||||
// FastForward will run the core as fast as possible
|
||||
var FastForward bool
|
||||
|
||||
var CoreSetSharedContext bool
|
||||
@@ -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"
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user