Update Playstation core (#73)

* Update cores setting for ps1

* Update PS3 output + use bytes per row to get the correct number of bytes per row

* Add more inputs

* Update input

* Fix input

* Add resize image comment

* Update screenshot

* Try side by side

* Update Image side by side

* Add image side by side

* Update landing page

* Add landing page image

* Update landing page image
This commit is contained in:
giongto35 2019-08-28 03:54:18 +08:00 committed by GitHub
parent 5175fde635
commit f491861874
24 changed files with 129 additions and 55 deletions

6
README.md vendored
View file

@ -14,8 +14,10 @@ This project aims to bring the most modern and convenient gaming experience to u
Note: **Due to the high cost of hosting, I will Hibernate the servers for a while. I'm working on a big change and will turn on hosting again. Sorry for that :(**
You can try hosting your own service following the instruction in the next session.
![screenshot](document/img/landing-page.gif)
![screenshot](document/img/landing-page-gb.png)
Screenshot | Screenshot
:-------------------------:|:-------------------------:
![screenshot](document/img/landing-page-ps-hm.png)|![screenshot](document/img/landing-page-ps-x4.png)
![screenshot](document/img/landing-page-gb.png)|![screenshot](document/img/landing-page.gif)
## Feature
1. Cloud gaming: Game logic is hosted on a remote server. User doesn't have to install or setup anything. Images and audio are streamed to user in the most optimal way.

View file

@ -23,7 +23,8 @@ var WSWait = 20 * time.Second
var MatchWorkerRandom = false
var ProdEnv = "prod"
var Codec = CODEC_H264
//var Codec = CODEC_H264
var Codec = CODEC_VP8
var FileTypeToEmulator = map[string]string{
"gba": "gba",
"cue": "pcsx",

View file

@ -3,6 +3,7 @@ package cws
import (
"encoding/json"
"log"
"runtime/debug"
"sync"
"time"
@ -96,6 +97,7 @@ func (c *Client) Receive(id string, f func(response WSPacket) (request WSPacket)
defer func() {
if err := recover(); err != nil {
log.Println("Recovered from err ", err)
log.Println(debug.Stack())
}
}()

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1 MiB

After

Width:  |  Height:  |  Size: 977 KiB

Before After
Before After

BIN
document/img/landing-page-ps-hm.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

BIN
document/img/landing-page-ps-x4.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

View file

@ -28,7 +28,7 @@ const (
left2
right2
)
const NumKeys = 8
const NumKeys = 10
// Audio consts
const (

1
go.mod vendored
View file

@ -5,6 +5,7 @@ go 1.12
require (
cloud.google.com/go v0.43.0
github.com/gen2brain/x264-go v0.0.0-20180306035800-58f586137654
github.com/go-gl/gl v0.0.0-20190320180904-bf2b1f2f34d7
github.com/gofrs/uuid v3.2.0+incompatible
github.com/gorilla/websocket v1.4.0
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect

2
go.sum vendored
View file

@ -18,6 +18,8 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/gen2brain/x264-go v0.0.0-20180306035800-58f586137654 h1:RNpogAT5Qz69YLIu6+92q5sSw61PjjhDj4upH2el5pk=
github.com/gen2brain/x264-go v0.0.0-20180306035800-58f586137654/go.mod h1:17kvfYQKi9/QHiKPeqmJW0YuDPZEgy72tSBVmweSyiE=
github.com/go-gl/gl v0.0.0-20190320180904-bf2b1f2f34d7 h1:SCYMcCJ89LjRGwEa0tRluNRiMjZHalQZrVrvTbPh+qw=
github.com/go-gl/gl v0.0.0-20190320180904-bf2b1f2f34d7/go.mod h1:482civXOzJJCPzJ4ZOX/pwvXBWSnzD4OKMdH4ClKGbk=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=

BIN
libretro/cores/mednafen_psx_hw_libretro.so vendored Executable file

Binary file not shown.

BIN
libretro/cores/mednafen_psx_libretro.so vendored Executable file

Binary file not shown.

BIN
libretro/cores/mednafen_snes_libretro.so vendored Executable file

Binary file not shown.

View file

@ -66,10 +66,9 @@ var NAEmulator *naEmulator
// TODO: Load from config
var emulatorCorePath = map[string]string{
"gba": "libretro/cores/mgba_libretro.so",
//"pcsx": "libretro/cores/mednafen_psx_libretro.so",
"gba": "libretro/cores/mgba_libretro.so",
"pcsx": "libretro/cores/mednafen_psx_libretro.so",
//"pcsx": "libretro/cores/mednafen_psx_hw_libretro.so",
"pcsx": "libretro/cores/pcsx_rearmed_libretro.so",
"arcade": "libretro/cores/fbalpha2012_neogeo_libretro.so",
"mame": "libretro/cores/mame2016_libretro.so",
}
@ -97,8 +96,12 @@ func (na *naEmulator) listenInput() {
// input from javascript follows bitmap. Ex: 00110101
// we decode the bitmap and send to channel
for inpBitmap := range NAEmulator.inputChannel {
for k := 0; k < len(na.keys); k++ {
key := bindRetroKeys[k]
for k := 0; k < len(bindRetroKeys); k++ {
key, ok := bindRetroKeys[k]
if ok == false {
continue
}
if (inpBitmap & 1) == 1 {
na.keys[key] = true
} else {

View file

@ -15,6 +15,7 @@ import (
"unsafe"
"github.com/giongto35/cloud-game/emulator"
"github.com/go-gl/gl/v2.1/gl"
)
/*
@ -71,20 +72,22 @@ var scale = 3.0
const bufSize = 1024 * 4
const joypadNumKeys = C.RETRO_DEVICE_ID_JOYPAD_R3
const joypadNumKeys = C.RETRO_DEVICE_ID_JOYPAD_R3 + 1
var joy [joypadNumKeys + 1]bool
var joy [joypadNumKeys]bool
var ewidth, eheight int
var bindRetroKeys = map[int]int{
0: C.RETRO_DEVICE_ID_JOYPAD_A,
1: C.RETRO_DEVICE_ID_JOYPAD_B,
2: C.RETRO_DEVICE_ID_JOYPAD_SELECT,
3: C.RETRO_DEVICE_ID_JOYPAD_START,
4: C.RETRO_DEVICE_ID_JOYPAD_UP,
5: C.RETRO_DEVICE_ID_JOYPAD_DOWN,
6: C.RETRO_DEVICE_ID_JOYPAD_LEFT,
7: C.RETRO_DEVICE_ID_JOYPAD_RIGHT,
2: C.RETRO_DEVICE_ID_JOYPAD_X,
3: C.RETRO_DEVICE_ID_JOYPAD_Y,
4: C.RETRO_DEVICE_ID_JOYPAD_SELECT,
5: C.RETRO_DEVICE_ID_JOYPAD_START,
6: C.RETRO_DEVICE_ID_JOYPAD_UP,
7: C.RETRO_DEVICE_ID_JOYPAD_DOWN,
8: C.RETRO_DEVICE_ID_JOYPAD_LEFT,
9: C.RETRO_DEVICE_ID_JOYPAD_RIGHT,
}
type CloudEmulator interface {
@ -121,47 +124,68 @@ func videoConfigure(geom *C.struct_retro_game_geometry) (int, int) {
fmt.Println("Failed to create the video texture")
}
video.pitch = uint32(geom.base_width) * video.bpp
return int(math.Round(nwidth)), int(math.Round(nheight))
}
//export coreVideoRefresh
func coreVideoRefresh(data unsafe.Pointer, width C.unsigned, height C.unsigned, pitch C.size_t) {
if uint32(pitch) != video.pitch {
video.pitch = uint32(pitch)
}
bytesPerRow := int(uint32(pitch) / video.bpp)
if data != nil {
NAEmulator.imageChannel <- toImageRGBA(data)
NAEmulator.imageChannel <- toImageRGBA(data, bytesPerRow)
}
}
// toImageRGBA convert nanoarch 2d array to image.RGBA
func toImageRGBA(data unsafe.Pointer) *image.RGBA {
func toImageRGBA(data unsafe.Pointer, bytesPerRow int) *image.RGBA {
// Convert unsafe Pointer to bytes array
var bytes []byte
// TODO: Investigate this
// seems like there is a padding of slice.
// If the resolution is 240 * 160. I have to convert to 256 * 160 slice.
// If the resolution is 320 * 240. I can keep it to 320 * 240.
// I'm making assumption that the slice is packed and it has padding to fill 64
var w = 0
for w < ewidth {
w += 64
}
sh := (*reflect.SliceHeader)(unsafe.Pointer(&bytes))
sh.Data = uintptr(data)
sh.Len = w * eheight * 2
sh.Cap = w * eheight * 2
sh.Len = bytesPerRow * eheight * 4
sh.Cap = bytesPerRow * eheight * 4
if video.pixFmt == gl.UNSIGNED_SHORT_5_6_5 {
return to565Image(data, bytes, bytesPerRow)
} else if video.pixFmt == gl.UNSIGNED_INT_8_8_8_8_REV {
return to8888Image(data, bytes, bytesPerRow)
}
return nil
}
func to8888Image(data unsafe.Pointer, bytes []byte, bytesPerRow int) *image.RGBA {
seek := 0
// Convert bytes array to image
// TODO: Reduce overhead of copying to bytes array by accessing unsafe.Pointer directly
image := image.NewRGBA(image.Rect(0, 0, ewidth, eheight))
for y := 0; y < eheight; y++ {
for x := 0; x < w; x++ {
for x := 0; x < bytesPerRow; x++ {
if x < ewidth {
b8 := bytes[seek]
g8 := bytes[seek+1]
r8 := bytes[seek+2]
a8 := bytes[seek+3]
image.Set(x, y, color.RGBA{byte(r8), byte(g8), byte(b8), byte(a8)})
}
seek += 4
}
}
// TODO: Resize Image
return image
}
func to565Image(data unsafe.Pointer, bytes []byte, bytesPerRow int) *image.RGBA {
seek := 0
// Convert bytes array to image
// TODO: Reduce overhead of copying to bytes array by accessing unsafe.Pointer directly
image := image.NewRGBA(image.Rect(0, 0, ewidth, eheight))
for y := 0; y < eheight; y++ {
for x := 0; x < bytesPerRow; x++ {
if x < ewidth {
var bi int
bi = (int)(bytes[seek]) + ((int)(bytes[seek+1]) << 8)
@ -179,14 +203,15 @@ func toImageRGBA(data unsafe.Pointer) *image.RGBA {
}
}
// TODO: Resize Image
return image
}
//export coreInputPoll
func coreInputPoll() {
for i := range NAEmulator.keys {
joy[i] = NAEmulator.keys[i]
}
//for i := range NAEmulator.keys {
//joy[i] = NAEmulator.keys[i]
//}
}
//export coreInputState
@ -195,7 +220,7 @@ func coreInputState(port C.unsigned, device C.unsigned, index C.unsigned, id C.u
return 0
}
if id < 255 && joy[id] {
if id < 255 && NAEmulator.keys[id] {
return 1
}
return 0
@ -261,8 +286,11 @@ func coreEnvironment(cmd C.unsigned, data unsafe.Pointer) C.bool {
if *format > C.RETRO_PIXEL_FORMAT_RGB565 {
return false
}
return true
return videoSetPixelFormat(*format)
case C.RETRO_ENVIRONMENT_GET_SYSTEM_DIRECTORY:
path := (**C.char)(data)
*path = C.CString("./libretro/system")
return true
case C.RETRO_ENVIRONMENT_GET_SAVE_DIRECTORY:
path := (**C.char)(data)
*path = C.CString(".")
@ -455,3 +483,32 @@ func nanoarchShutdown() {
func nanoarchRun() {
C.bridge_retro_run(retroRun)
}
func videoSetPixelFormat(format uint32) C.bool {
if video.texID != 0 {
log.Fatal("Tried to change pixel format after initialization.")
}
switch format {
case C.RETRO_PIXEL_FORMAT_0RGB1555:
video.pixFmt = gl.UNSIGNED_SHORT_5_5_5_1
video.pixType = gl.BGRA
video.bpp = 2
break
case C.RETRO_PIXEL_FORMAT_XRGB8888:
video.pixFmt = gl.UNSIGNED_INT_8_8_8_8_REV
video.pixType = gl.BGRA
video.bpp = 4
break
case C.RETRO_PIXEL_FORMAT_RGB565:
video.pixFmt = gl.UNSIGNED_SHORT_5_6_5
video.pixType = gl.RGB
video.bpp = 2
break
default:
log.Fatalf("Unknown pixel type %v", format)
}
fmt.Printf("Video pixel: %v %v %v %v %v", video, format, C.RETRO_PIXEL_FORMAT_0RGB1555, C.RETRO_PIXEL_FORMAT_XRGB8888, C.RETRO_PIXEL_FORMAT_RGB565)
return true
}

BIN
libretro/system/scph5500.bin vendored Normal file

Binary file not shown.

BIN
libretro/system/scph5501.bin vendored Normal file

Binary file not shown.

BIN
libretro/system/scph5502.bin vendored Normal file

Binary file not shown.

View file

@ -129,9 +129,9 @@ function sendKeyState() {
console.log(`Key state string: ${bits} ==> ${data}`);
// send packed keystate
var arrBuf = new Uint8Array(1);
arrBuf[0] = data;
var arrBuf = new Uint8Array(2);
arrBuf[0] = data & ((1 << 8) - 1);
arrBuf[1] = data >> 8;
inputChannel.send(arrBuf);
unchangePacket--;
@ -193,6 +193,8 @@ function doButtonUp(name) {
case "join":
case "a":
case "b":
case "x":
case "y":
case "start":
case "select":
startGame();

View file

@ -10,8 +10,10 @@ const KEYBOARD_MAP = {
90: "a", // z
88: "b", // x
67: "start", // c
86: "select", // v
67: "x", // c
86: "y", // v
13: "start", // start
16: "select", // select
// non-game
81: "quit", // q

4
static/js/global.js vendored
View file

@ -3,7 +3,7 @@
*/
const DEBUG = false;
const KEY_BIT = ["a", "b", "select", "start", "up", "down", "left", "right"];
const KEY_BIT = ["a", "b", "x", "y", "select", "start", "up", "down", "left", "right"];
const INPUT_FPS = 100;
const INPUT_STATE_PACKET = 1;
const PINGPONGPS = 5;
@ -29,6 +29,8 @@ let keyState = {
// control
a: false,
b: false,
x: false,
y: false,
start: false,
select: false,

View file

@ -60,7 +60,7 @@ func isValidGameType(gamePath string) bool {
// getGameInfo returns game info from a path
func getGameInfo(path string) GameInfo {
// Remove prefix to obtain file names
fileName := path[len(gamePath)+1:]
fileName := filepath.Base(path)
ext := filepath.Ext(fileName)
return GameInfo{
Name: strings.TrimSuffix(fileName, ext),

View file

@ -155,8 +155,8 @@ func (w *WebRTC) StartClient(remoteSession string, iceCandidates []string) (stri
// Register text message handling
inputTrack.OnMessage(func(msg webrtc.DataChannelMessage) {
// TODO: Can add recover here
w.InputChannel <- int(msg.Data[0])
// TODO: Can add recover here + generalize
w.InputChannel <- int(msg.Data[1])<<8 + int(msg.Data[0])
})
inputTrack.OnClose(func() {

View file

@ -88,15 +88,14 @@ func (h *Handler) GetOverlordClient() *OverlordClient {
// detachPeerConn detach/remove a peerconnection from current room
func (h *Handler) detachPeerConn(pc *webrtc.WebRTC) {
log.Println("Detach peerconnection")
roomID := pc.RoomID
room := h.getRoom(roomID)
room := h.getRoom(pc.RoomID)
if room == nil {
return
}
// If room has no sessions, close room
if !room.EmptySessions() {
room.RemoveSession(pc)
// If no more session in that room, we close that room
if room.EmptySessions() {
log.Println("No session in room")
room.Close()

View file

@ -57,6 +57,7 @@ func NewRoom(roomID string, gameName string, onlineStorage *storage.Client) *Roo
roomID = generateRoomID(gameName)
} else {
gameName = getGameNameFromRoomID(roomID)
log.Println("Get Gamename from RoomID", gameName)
}
gameInfo := gamelist.GetGameInfoFromName(gameName)
@ -168,9 +169,9 @@ func (r *Room) startWebRTCSession(peerconnection *webrtc.WebRTC, playerIndex int
}
if peerconnection.IsConnected() {
// the first 8 bits belong to player 1
// the next 8 belongs to player 2 ...
// We standardize and put it to inputChannel (16 bits)
// the first 10 bits belong to player 1
// the next 10 belongs to player 2 ...
// We standardize and put it to inputChannel (20 bits)
input = input << ((uint(playerIndex) - 1) * emulator.NumKeys)
select {
case r.inputChannel <- input: