mirror of
https://github.com/giongto35/cloud-game.git
synced 2026-01-23 02:34:42 +00:00
Merge aspect ratio to master (#116)
* Add frame scaling support (#107) * Update README.md with additional info about Windows builds * Add frame scaling * Add and enable bilinear scaling (#109) * Add and enable bilinear scaling * Use Go x/image lib for interpolation * Reformat the code goimport/gofmt * Move worker config into the pkg/worker directory * Change separator in the save file path allowing it to work on Windows (#113)
This commit is contained in:
parent
08999ee3c2
commit
fde4a24158
14 changed files with 312 additions and 140 deletions
|
|
@ -7,6 +7,8 @@ import (
|
|||
"os/signal"
|
||||
"time"
|
||||
|
||||
config "github.com/giongto35/cloud-game/pkg/config/worker"
|
||||
|
||||
"github.com/giongto35/cloud-game/pkg/util/logging"
|
||||
"github.com/giongto35/cloud-game/pkg/worker"
|
||||
"github.com/golang/glog"
|
||||
|
|
@ -16,7 +18,7 @@ import (
|
|||
func main() {
|
||||
rand.Seed(time.Now().UTC().UnixNano())
|
||||
|
||||
cfg := worker.NewDefaultConfig()
|
||||
cfg := config.NewDefaultConfig()
|
||||
cfg.AddFlags(pflag.CommandLine)
|
||||
|
||||
logging.Init()
|
||||
|
|
|
|||
1
go.mod
vendored
1
go.mod
vendored
|
|
@ -11,5 +11,6 @@ require (
|
|||
github.com/pion/webrtc/v2 v2.1.2
|
||||
github.com/prometheus/client_golang v1.1.0
|
||||
github.com/spf13/pflag v1.0.3
|
||||
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8
|
||||
gopkg.in/hraban/opus.v2 v2.0.0-20180426093920-0f2e0b4fc6cd
|
||||
)
|
||||
|
|
|
|||
3
go.sum
vendored
3
go.sum
vendored
|
|
@ -152,7 +152,10 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5 h1:58fnuSXlxZmFdJyvtTFVmV
|
|||
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067 h1:KYGJGHOQy8oSi1fDlSpcZF0+juKwk/hEMv5SiwHogR0=
|
||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8 h1:hVwzHzIUGRjiF7EcUjqNxk3NCfkPxbDKRdnNE1Rpg0U=
|
||||
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
|
|
|
|||
|
|
@ -47,6 +47,9 @@ type EmulatorMeta struct {
|
|||
Height int
|
||||
AudioSampleRate int
|
||||
Fps int
|
||||
BaseWidth int
|
||||
BaseHeight int
|
||||
Ratio float64
|
||||
}
|
||||
|
||||
var EmulatorConfig = map[string]EmulatorMeta{
|
||||
|
|
|
|||
|
|
@ -9,13 +9,23 @@ type Config struct {
|
|||
Port int
|
||||
OverlordAddress string
|
||||
|
||||
// video
|
||||
Scale int
|
||||
DisableCustomSize bool
|
||||
Width int
|
||||
Height int
|
||||
|
||||
MonitoringConfig monitoring.ServerMonitoringConfig
|
||||
}
|
||||
|
||||
func NewDefaultConfig() Config {
|
||||
return Config{
|
||||
Port: 8800,
|
||||
OverlordAddress: "ws://localhost:8000/wso",
|
||||
Port: 8800,
|
||||
OverlordAddress: "ws://localhost:8000/wso",
|
||||
Scale: 1,
|
||||
DisableCustomSize: false,
|
||||
Width: 320,
|
||||
Height: 240,
|
||||
MonitoringConfig: monitoring.ServerMonitoringConfig{
|
||||
Port: 6601,
|
||||
URLPrefix: "/worker",
|
||||
|
|
@ -28,6 +38,11 @@ func (c *Config) AddFlags(fs *pflag.FlagSet) *Config {
|
|||
fs.IntVarP(&c.Port, "port", "", 8800, "OverWorker server port")
|
||||
fs.StringVarP(&c.OverlordAddress, "overlordhost", "", c.OverlordAddress, "OverWorker URL to connect")
|
||||
|
||||
fs.IntVarP(&c.Scale, "scale", "s", c.Scale, "Set output viewport scale factor")
|
||||
fs.BoolVarP(&c.DisableCustomSize, "disable-custom-size", "", c.DisableCustomSize, "Disable custom size")
|
||||
fs.IntVarP(&c.Width, "width", "w", c.Width, "Set custom viewport width")
|
||||
fs.IntVarP(&c.Height, "height", "h", c.Height, "Set custom viewport height")
|
||||
|
||||
fs.BoolVarP(&c.MonitoringConfig.MetricEnabled, "monitoring.metric", "m", c.MonitoringConfig.MetricEnabled, "Enable prometheus metric for server")
|
||||
fs.BoolVarP(&c.MonitoringConfig.ProfilingEnabled, "monitoring.pprof", "p", c.MonitoringConfig.ProfilingEnabled, "Enable golang pprof for server")
|
||||
fs.IntVarP(&c.MonitoringConfig.Port, "monitoring.port", "", c.MonitoringConfig.Port, "Monitoring server port")
|
||||
36
pkg/emulator/libretro/image/color.go
Normal file
36
pkg/emulator/libretro/image/color.go
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
package image
|
||||
|
||||
import (
|
||||
"image/color"
|
||||
)
|
||||
|
||||
const (
|
||||
// BIT_FORMAT_SHORT_5_5_5_1 has 5 bits R, 5 bits G, 5 bits B, 1 bit alpha
|
||||
BIT_FORMAT_SHORT_5_5_5_1 = iota
|
||||
// BIT_FORMAT_INT_8_8_8_8_REV has 8 bits R, 8 bits G, 8 bits B, 8 bit alpha
|
||||
BIT_FORMAT_INT_8_8_8_8_REV
|
||||
// BIT_FORMAT_SHORT_5_6_5 has 5 bits R, 6 bits G, 5 bits
|
||||
BIT_FORMAT_SHORT_5_6_5
|
||||
)
|
||||
|
||||
type Format func(data []byte, index int) color.RGBA
|
||||
|
||||
func rgb565(data []byte, index int) color.RGBA {
|
||||
pixel := (int)(data[index]) + ((int)(data[index+1]) << 8)
|
||||
|
||||
return color.RGBA{
|
||||
R: byte(((pixel>>11)*255 + 15) / 31),
|
||||
G: byte((((pixel>>5)&0x3F)*255 + 31) / 63),
|
||||
B: byte(((pixel&0x1F)*255 + 15) / 31),
|
||||
A: 255,
|
||||
}
|
||||
}
|
||||
|
||||
func rgba8888(data []byte, index int) color.RGBA {
|
||||
return color.RGBA{
|
||||
R: data[index+2],
|
||||
G: data[index+1],
|
||||
B: data[index],
|
||||
A: data[index+3],
|
||||
}
|
||||
}
|
||||
18
pkg/emulator/libretro/image/draw.go
Normal file
18
pkg/emulator/libretro/image/draw.go
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
package image
|
||||
|
||||
import (
|
||||
"image"
|
||||
)
|
||||
|
||||
func DrawRgbaImage(pixFormat int, scaleType int, w int, h int, packedW int, vw int, vh int, bpp int, data []byte, image *image.RGBA) {
|
||||
switch pixFormat {
|
||||
case BIT_FORMAT_SHORT_5_6_5:
|
||||
Resize(scaleType, rgb565, w, h, packedW, vw, vh, bpp, data, image)
|
||||
case BIT_FORMAT_INT_8_8_8_8_REV:
|
||||
Resize(scaleType, rgba8888, w, h, packedW, vw, vh, bpp, data, image)
|
||||
case BIT_FORMAT_SHORT_5_5_5_1:
|
||||
fallthrough
|
||||
default:
|
||||
image = nil
|
||||
}
|
||||
}
|
||||
135
pkg/emulator/libretro/image/scale.go
Normal file
135
pkg/emulator/libretro/image/scale.go
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
package image
|
||||
|
||||
import (
|
||||
"image"
|
||||
"image/color"
|
||||
|
||||
"golang.org/x/image/draw"
|
||||
)
|
||||
|
||||
const (
|
||||
// skips image interpolation
|
||||
ScaleSkip = -1
|
||||
// initial image interpolation algorithm
|
||||
ScaleOld = 0
|
||||
// nearest neighbour interpolation
|
||||
ScaleNearestNeighbour = 1
|
||||
// bilinear interpolation
|
||||
ScaleBilinear = 2
|
||||
)
|
||||
|
||||
func Resize(scaleType int, fn Format, w int, h int, packedW int, vw int, vh int, bpp int, data []byte, out *image.RGBA) {
|
||||
|
||||
// !to implement own image interfaces img.Pix = bytes[]
|
||||
src := image.NewRGBA(image.Rect(0, 0, w, h))
|
||||
toRgba(fn, w, h, packedW, bpp, data, src)
|
||||
|
||||
// !to do set it once instead switching on each iteration
|
||||
// !to do skip resize if w=vw h=vh
|
||||
switch scaleType {
|
||||
case ScaleSkip:
|
||||
skip(fn, w, h, packedW, vw, vh, bpp, data, src, out)
|
||||
case ScaleNearestNeighbour:
|
||||
draw.NearestNeighbor.Scale(out, out.Bounds(), src, src.Bounds(), draw.Src, nil)
|
||||
//nearest(fn, w, h, packedW, vw, vh, bpp, data, src, out)
|
||||
case ScaleBilinear:
|
||||
draw.ApproxBiLinear.Scale(out, out.Bounds(), src, src.Bounds(), draw.Src, nil)
|
||||
//bilinear(fn, w, h, packedW, vw, vh, bpp, data, src, out)
|
||||
case ScaleOld:
|
||||
fallthrough
|
||||
default:
|
||||
old(fn, w, h, packedW, vw, vh, bpp, data, src, out)
|
||||
}
|
||||
}
|
||||
|
||||
func old(fn Format, w int, h int, packedW int, vw int, vh int, bpp int, data []byte, _ *image.RGBA, out *image.RGBA) {
|
||||
seek := 0
|
||||
|
||||
scaleWidth := float64(vw) / float64(w)
|
||||
scaleHeight := float64(vh) / float64(h)
|
||||
|
||||
for y := 0; y < h; y++ {
|
||||
y2 := int(float64(y) * scaleHeight)
|
||||
for x := 0; x < packedW; x++ {
|
||||
x2 := int(float64(x) * scaleWidth)
|
||||
if x2 < vw {
|
||||
out.Set(x2, y2, fn(data, seek))
|
||||
}
|
||||
|
||||
seek += bpp
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func skip(fn Format, w int, h int, packedW int, _ int, _ int, bpp int, data []byte, _ *image.RGBA, out *image.RGBA) {
|
||||
for y := 0; y < h; y++ {
|
||||
for x := 0; x < w; x++ {
|
||||
index := (y * packedW) + x
|
||||
index *= bpp
|
||||
out.Set(x, y, fn(data, index))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func nearest(fn Format, w int, h int, packedW int, vw int, vh int, bpp int, data []byte, _ *image.RGBA, out *image.RGBA) {
|
||||
xRatio := ((w << 16) / vw) + 1
|
||||
yRatio := ((h << 16) / vh) + 1
|
||||
|
||||
for y := 0; y < vh; y++ {
|
||||
y2 := (y * yRatio) >> 16
|
||||
for x := 0; x < vw; x++ {
|
||||
x2 := (x * xRatio) >> 16
|
||||
|
||||
index := (y2 * packedW) + x2
|
||||
index *= bpp
|
||||
|
||||
out.Set(x, y, fn(data, index))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This implementation has some color bleeding issues
|
||||
func bilinear(fn Format, w int, h int, packedW int, vw int, vh int, bpp int, data []byte, _ *image.RGBA, out *image.RGBA) {
|
||||
xRatio := float64(w-1) / float64(vw)
|
||||
yRatio := float64(h-1) / float64(vh)
|
||||
|
||||
for y := 0; y < vh; y++ {
|
||||
y2 := int(yRatio * float64(y))
|
||||
for x := 0; x < vw; x++ {
|
||||
x2 := int(xRatio * float64(x))
|
||||
|
||||
w := (xRatio * float64(x)) - float64(x2)
|
||||
h := (yRatio * float64(y)) - float64(y2)
|
||||
|
||||
index := (y2 * packedW) + x2
|
||||
|
||||
a := fn(data, index*bpp)
|
||||
b := fn(data, (index+1)*bpp)
|
||||
c := fn(data, (index+packedW)*bpp)
|
||||
d := fn(data, (index+packedW+1)*bpp)
|
||||
|
||||
out.Set(x, y, color.RGBA{
|
||||
// don't sink the boat
|
||||
R: byte(float64(a.R)*(1-w)*(1-h) + float64(b.R)*w*(1-h) + float64(c.R)*h*(1-w) + float64(d.R)*w*h),
|
||||
G: byte(float64(a.G)*(1-w)*(1-h) + float64(b.G)*w*(1-h) + float64(c.G)*h*(1-w) + float64(d.G)*w*h),
|
||||
B: byte(float64(a.B)*(1-w)*(1-h) + float64(b.B)*w*(1-h) + float64(c.B)*h*(1-w) + float64(d.B)*w*h),
|
||||
//A: byte(float64(a.A)*(1-w)*(1-h) + float64(b.A)*w*(1-h) + float64(c.A)*h*(1-w) + float64(d.A)*w*h),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func toRgba(fn Format, w int, h int, packedW int, bpp int, data []byte, image *image.RGBA) {
|
||||
for y := 0; y < h; y++ {
|
||||
for x := 0; x < w; x++ {
|
||||
index := (y*packedW + x) * bpp
|
||||
c := fn(data, index)
|
||||
i := (y-image.Rect.Min.Y)*image.Stride + (x-image.Rect.Min.X)*4
|
||||
s := image.Pix[i : i+4 : i+4]
|
||||
s[0] = c.R
|
||||
s[1] = c.G
|
||||
s[2] = c.B
|
||||
s[3] = c.A
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -67,10 +67,6 @@ var outputImg *image.RGBA
|
|||
// NAEmulator implements CloudEmulator interface based on NanoArch(golang RetroArch)
|
||||
func NewNAEmulator(etype string, roomID string, imageChannel chan<- *image.RGBA, audioChannel chan<- []int16, inputChannel <-chan int) *naEmulator {
|
||||
meta := config.EmulatorConfig[etype]
|
||||
ewidth = meta.Width
|
||||
eheight = meta.Height
|
||||
// outputImg is tmp img used for decoding and reuse in encoding flow
|
||||
outputImg = image.NewRGBA(image.Rect(0, 0, ewidth, eheight))
|
||||
|
||||
return &naEmulator{
|
||||
meta: meta,
|
||||
|
|
@ -117,6 +113,14 @@ func (na *naEmulator) LoadMeta(path string) config.EmulatorMeta {
|
|||
return na.meta
|
||||
}
|
||||
|
||||
func (na *naEmulator) SetViewport(width int, height int) {
|
||||
// outputImg is tmp img used for decoding and reuse in encoding flow
|
||||
outputImg = image.NewRGBA(image.Rect(0, 0, width, height))
|
||||
|
||||
ewidth = width
|
||||
eheight = height
|
||||
}
|
||||
|
||||
func (na *naEmulator) Start() {
|
||||
na.playGame(na.gamePath)
|
||||
ticker := time.NewTicker(time.Second / 60)
|
||||
|
|
|
|||
|
|
@ -4,14 +4,13 @@ import (
|
|||
"bufio"
|
||||
"errors"
|
||||
"fmt"
|
||||
"image"
|
||||
"image/color"
|
||||
"log"
|
||||
"os"
|
||||
"os/user"
|
||||
"reflect"
|
||||
"sync"
|
||||
"unsafe"
|
||||
|
||||
"github.com/giongto35/cloud-game/pkg/emulator/libretro/image"
|
||||
)
|
||||
|
||||
/*
|
||||
|
|
@ -63,8 +62,6 @@ var video struct {
|
|||
bpp uint32
|
||||
}
|
||||
|
||||
var scale = 3.0
|
||||
|
||||
const bufSize = 1024 * 4
|
||||
|
||||
const joypadNumKeys = int(C.RETRO_DEVICE_ID_JOYPAD_R3 + 1)
|
||||
|
|
@ -85,15 +82,6 @@ var bindRetroKeys = map[int]int{
|
|||
9: C.RETRO_DEVICE_ID_JOYPAD_RIGHT,
|
||||
}
|
||||
|
||||
const (
|
||||
// BIT_FORMAT_SHORT_5_5_5_1 has 5 bits R, 5 bits G, 5 bits B, 1 bit alpha
|
||||
BIT_FORMAT_SHORT_5_5_5_1 = iota
|
||||
// BIT_FORMAT_INT_8_8_8_8_REV has 8 bits R, 8 bits G, 8 bits B, 8 bit alpha
|
||||
BIT_FORMAT_INT_8_8_8_8_REV
|
||||
// BIT_FORMAT_SHORT_5_6_5 has 5 bits R, 6 bits G, 5 bits
|
||||
BIT_FORMAT_SHORT_5_6_5
|
||||
)
|
||||
|
||||
type CloudEmulator interface {
|
||||
Start(path string)
|
||||
SaveGame(saveExtraFunc func() error) error
|
||||
|
|
@ -102,109 +90,25 @@ type CloudEmulator interface {
|
|||
Close()
|
||||
}
|
||||
|
||||
func resizeToAspect(ratio float64, sw float64, sh float64) (dw float64, dh float64) {
|
||||
if ratio <= 0 {
|
||||
ratio = sw / sh
|
||||
}
|
||||
|
||||
if sw/sh < 1.0 {
|
||||
dw = dh * ratio
|
||||
dh = sh
|
||||
} else {
|
||||
dw = sw
|
||||
dh = dw / ratio
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
//export coreVideoRefresh
|
||||
func coreVideoRefresh(data unsafe.Pointer, width C.unsigned, height C.unsigned, pitch C.size_t) {
|
||||
bytesPerRow := int(uint32(pitch) / video.bpp)
|
||||
|
||||
if data != nil {
|
||||
NAEmulator.imageChannel <- toImageRGBA(data, bytesPerRow, int(width), int(height))
|
||||
}
|
||||
}
|
||||
|
||||
// toImageRGBA convert nanoarch 2d array to image.RGBA
|
||||
func toImageRGBA(data unsafe.Pointer, bytesPerRow int, inputWidth, inputHeight int) *image.RGBA {
|
||||
// Convert unsafe Pointer to bytes array
|
||||
var bytes []byte
|
||||
|
||||
// Convert bytes array to image
|
||||
// TODO: Reduce overhead of copying to bytes array by accessing unsafe.Pointer directly
|
||||
sh := (*reflect.SliceHeader)(unsafe.Pointer(&bytes))
|
||||
sh.Data = uintptr(data)
|
||||
sh.Len = bytesPerRow * inputHeight * 4
|
||||
sh.Cap = bytesPerRow * inputHeight * 4
|
||||
|
||||
if video.pixFmt == BIT_FORMAT_SHORT_5_6_5 {
|
||||
return to565Image(data, bytes, bytesPerRow, inputWidth, inputHeight)
|
||||
} else if video.pixFmt == BIT_FORMAT_INT_8_8_8_8_REV {
|
||||
return to8888Image(data, bytes, bytesPerRow, inputWidth, inputHeight)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func to8888Image(data unsafe.Pointer, bytes []byte, bytesPerRow int, inputWidth, inputHeight int) *image.RGBA {
|
||||
seek := 0
|
||||
|
||||
// scaleWidth and scaleHeight is the scale
|
||||
scaleWidth := float64(ewidth) / float64(inputWidth)
|
||||
scaleHeight := float64(eheight) / float64(inputHeight)
|
||||
|
||||
for y := 0; y < inputHeight; y++ {
|
||||
for x := 0; x < bytesPerRow; x++ {
|
||||
xx := int(float64(x) * scaleWidth)
|
||||
yy := int(float64(y) * scaleHeight)
|
||||
if xx < ewidth {
|
||||
b8 := bytes[seek]
|
||||
g8 := bytes[seek+1]
|
||||
r8 := bytes[seek+2]
|
||||
a8 := bytes[seek+3]
|
||||
|
||||
outputImg.Set(xx, yy, color.RGBA{byte(r8), byte(g8), byte(b8), byte(a8)})
|
||||
}
|
||||
|
||||
seek += 4
|
||||
}
|
||||
// some cores can return nothing
|
||||
if data == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: Resize Image
|
||||
return outputImg
|
||||
}
|
||||
// calculate real frame width in pixels from packed data (realWidth >= width)
|
||||
packedWidth := int(uint32(pitch) / video.bpp)
|
||||
|
||||
func to565Image(data unsafe.Pointer, bytes []byte, bytesPerRow int, inputWidth, inputHeight int) *image.RGBA {
|
||||
seek := 0
|
||||
// convert data from C
|
||||
bytes := int(height) * packedWidth * int(video.bpp)
|
||||
data_ := (*[1 << 30]byte)(data)[:bytes:bytes]
|
||||
|
||||
// scaleWidth and scaleHeight is the scale
|
||||
scaleWidth := float64(ewidth) / float64(inputWidth)
|
||||
scaleHeight := float64(eheight) / float64(inputHeight)
|
||||
// !to move it on the other side of the channel
|
||||
image.DrawRgbaImage(int(video.pixFmt), image.ScaleBilinear, int(width), int(height),
|
||||
packedWidth, ewidth, eheight, int(video.bpp), data_, outputImg)
|
||||
|
||||
for y := 0; y < inputHeight; y++ {
|
||||
for x := 0; x < bytesPerRow; x++ {
|
||||
xx := int(float64(x) * scaleWidth)
|
||||
yy := int(float64(y) * scaleHeight)
|
||||
if xx < ewidth {
|
||||
var bi int
|
||||
bi = (int)(bytes[seek]) + ((int)(bytes[seek+1]) << 8)
|
||||
b5 := bi & 0x1F
|
||||
g6 := (bi >> 5) & 0x3F
|
||||
r5 := (bi >> 11)
|
||||
|
||||
b8 := (b5*255 + 15) / 31
|
||||
g8 := (g6*255 + 31) / 63
|
||||
r8 := (r5*255 + 15) / 31
|
||||
|
||||
outputImg.Set(xx, yy, color.RGBA{byte(r8), byte(g8), byte(b8), 255})
|
||||
}
|
||||
|
||||
seek += 2
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Resize Image
|
||||
return outputImg
|
||||
NAEmulator.imageChannel <- outputImg
|
||||
}
|
||||
|
||||
//export coreInputPoll
|
||||
|
|
@ -434,15 +338,25 @@ func coreLoadGame(filename string) {
|
|||
|
||||
C.bridge_retro_get_system_av_info(retroGetSystemAVInfo, &avi)
|
||||
|
||||
// Append the library name to the window title.
|
||||
NAEmulator.meta.AudioSampleRate = int(avi.timing.sample_rate)
|
||||
NAEmulator.meta.Fps = int(avi.timing.fps)
|
||||
NAEmulator.meta.BaseWidth = int(avi.geometry.base_width)
|
||||
NAEmulator.meta.BaseHeight = int(avi.geometry.base_height)
|
||||
// set aspect ratio
|
||||
/* Nominal aspect ratio of game. If aspect_ratio is <= 0.0,
|
||||
an aspect ratio of base_width / base_height is assumed.
|
||||
* A frontend could override this setting, if desired. */
|
||||
ratio := float64(avi.geometry.aspect_ratio)
|
||||
if ratio <= 0.0 {
|
||||
ratio = float64(avi.geometry.base_width) / float64(avi.geometry.base_height)
|
||||
}
|
||||
NAEmulator.meta.Ratio = ratio
|
||||
|
||||
fmt.Println("-----------------------------------")
|
||||
fmt.Println("--- System audio and video info ---")
|
||||
fmt.Println("-----------------------------------")
|
||||
fmt.Println(" Aspect ratio: ", avi.geometry.aspect_ratio)
|
||||
/* Nominal aspect ratio of game. If
|
||||
* aspect_ratio is <= 0.0, an aspect ratio
|
||||
* of base_width / base_height is assumed.
|
||||
* A frontend could override this setting,
|
||||
* if desired. */
|
||||
fmt.Println(" Aspect ratio: ", ratio)
|
||||
fmt.Println(" Base width: ", avi.geometry.base_width) /* Nominal video width of game. */
|
||||
fmt.Println(" Base height: ", avi.geometry.base_height) /* Nominal video height of game. */
|
||||
fmt.Println(" Max width: ", avi.geometry.max_width) /* Maximum possible width of game. */
|
||||
|
|
@ -450,10 +364,6 @@ func coreLoadGame(filename string) {
|
|||
fmt.Println(" Sample rate: ", avi.timing.sample_rate) /* Sampling rate of audio. */
|
||||
fmt.Println(" FPS: ", avi.timing.fps) /* FPS of video content. */
|
||||
fmt.Println("-----------------------------------")
|
||||
|
||||
// Append the library name to the window title.
|
||||
NAEmulator.meta.AudioSampleRate = int(avi.timing.sample_rate)
|
||||
NAEmulator.meta.Fps = int(avi.timing.fps)
|
||||
}
|
||||
|
||||
// serializeSize returns the amount of data the implementation requires to serialize
|
||||
|
|
@ -500,15 +410,15 @@ func nanoarchRun() {
|
|||
func videoSetPixelFormat(format uint32) C.bool {
|
||||
switch format {
|
||||
case C.RETRO_PIXEL_FORMAT_0RGB1555:
|
||||
video.pixFmt = BIT_FORMAT_SHORT_5_5_5_1
|
||||
video.pixFmt = image.BIT_FORMAT_SHORT_5_5_5_1
|
||||
video.bpp = 2
|
||||
break
|
||||
case C.RETRO_PIXEL_FORMAT_XRGB8888:
|
||||
video.pixFmt = BIT_FORMAT_INT_8_8_8_8_REV
|
||||
video.pixFmt = image.BIT_FORMAT_INT_8_8_8_8_REV
|
||||
video.bpp = 4
|
||||
break
|
||||
case C.RETRO_PIXEL_FORMAT_RGB565:
|
||||
video.pixFmt = BIT_FORMAT_SHORT_5_6_5
|
||||
video.pixFmt = image.BIT_FORMAT_SHORT_5_6_5
|
||||
video.bpp = 2
|
||||
break
|
||||
default:
|
||||
|
|
|
|||
|
|
@ -7,6 +7,9 @@ type CloudEmulator interface {
|
|||
// LoadMeta returns meta data of emulator. Refer below
|
||||
LoadMeta(path string) config.EmulatorMeta
|
||||
// Start is called after LoadGame
|
||||
|
||||
SetViewport(width int, height int)
|
||||
|
||||
Start()
|
||||
// SaveGame save game state, saveExtraFunc is callback to do extra step. Ex: save to google cloud
|
||||
SaveGame(saveExtraFunc func() error) error
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@ import (
|
|||
"path"
|
||||
"time"
|
||||
|
||||
"github.com/giongto35/cloud-game/pkg/config/worker"
|
||||
|
||||
"github.com/giongto35/cloud-game/pkg/util"
|
||||
"github.com/giongto35/cloud-game/pkg/webrtc"
|
||||
storage "github.com/giongto35/cloud-game/pkg/worker/cloud-storage"
|
||||
|
|
@ -26,6 +28,7 @@ type Handler struct {
|
|||
oClient *OverlordClient
|
||||
// Raw address of overlord
|
||||
overlordHost string
|
||||
cfg worker.Config
|
||||
// Rooms map : RoomID -> Room
|
||||
rooms map[string]*room.Room
|
||||
// ID of the current server globalwise
|
||||
|
|
@ -37,7 +40,7 @@ type Handler struct {
|
|||
}
|
||||
|
||||
// NewHandler returns a new server
|
||||
func NewHandler(overlordHost string) *Handler {
|
||||
func NewHandler(cfg worker.Config) *Handler {
|
||||
// Create offline storage folder
|
||||
createOfflineStorage()
|
||||
|
||||
|
|
@ -46,7 +49,8 @@ func NewHandler(overlordHost string) *Handler {
|
|||
return &Handler{
|
||||
rooms: map[string]*room.Room{},
|
||||
sessions: map[string]*Session{},
|
||||
overlordHost: overlordHost,
|
||||
overlordHost: cfg.OverlordAddress,
|
||||
cfg: cfg,
|
||||
onlineStorage: onlineStorage,
|
||||
}
|
||||
}
|
||||
|
|
@ -135,7 +139,7 @@ func (h *Handler) createNewRoom(gameName string, roomID string, playerIndex int,
|
|||
// or the roomID doesn't have any running sessions (room was closed)
|
||||
// we spawn a new room
|
||||
if roomID == "" || !h.isRoomRunning(roomID) {
|
||||
room := room.NewRoom(roomID, gameName, videoEncoderType, h.onlineStorage)
|
||||
room := room.NewRoom(roomID, gameName, videoEncoderType, h.onlineStorage, h.cfg)
|
||||
// TODO: Might have race condition
|
||||
h.rooms[room.ID] = room
|
||||
return room
|
||||
|
|
|
|||
|
|
@ -8,18 +8,20 @@ import (
|
|||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/giongto35/cloud-game/pkg/config/worker"
|
||||
|
||||
"github.com/giongto35/cloud-game/pkg/monitoring"
|
||||
"github.com/golang/glog"
|
||||
)
|
||||
|
||||
type OverWorker struct {
|
||||
ctx context.Context
|
||||
cfg Config
|
||||
cfg worker.Config
|
||||
|
||||
monitoringServer *monitoring.ServerMonitoring
|
||||
}
|
||||
|
||||
func New(ctx context.Context, cfg Config) *OverWorker {
|
||||
func New(ctx context.Context, cfg worker.Config) *OverWorker {
|
||||
return &OverWorker{
|
||||
ctx: ctx,
|
||||
cfg: cfg,
|
||||
|
|
@ -50,7 +52,7 @@ func (o *OverWorker) Shutdown() {
|
|||
|
||||
// initializeWorker setup a worker
|
||||
func (o *OverWorker) initializeWorker() {
|
||||
worker := NewHandler(o.cfg.OverlordAddress)
|
||||
worker := NewHandler(o.cfg)
|
||||
|
||||
defer func() {
|
||||
log.Println("Close worker")
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import (
|
|||
"image"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"math"
|
||||
"math/rand"
|
||||
"os"
|
||||
"runtime"
|
||||
|
|
@ -11,6 +12,8 @@ import (
|
|||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/giongto35/cloud-game/pkg/config/worker"
|
||||
|
||||
"github.com/giongto35/cloud-game/pkg/config"
|
||||
"github.com/giongto35/cloud-game/pkg/emulator"
|
||||
"github.com/giongto35/cloud-game/pkg/emulator/libretro/nanoarch"
|
||||
|
|
@ -49,8 +52,10 @@ type Room struct {
|
|||
//meta emulator.Meta
|
||||
}
|
||||
|
||||
const separator = "___"
|
||||
|
||||
// NewRoom creates a new room
|
||||
func NewRoom(roomID string, gameName string, videoEncoderType string, onlineStorage *storage.Client) *Room {
|
||||
func NewRoom(roomID string, gameName string, videoEncoderType string, onlineStorage *storage.Client, cfg worker.Config) *Room {
|
||||
// If no roomID is given, generate it from gameName
|
||||
// If the is roomID, get gameName from roomID
|
||||
if roomID == "" {
|
||||
|
|
@ -101,7 +106,27 @@ func NewRoom(roomID string, gameName string, videoEncoderType string, onlineStor
|
|||
room.director = getEmulator(emuName, roomID, imageChannel, audioChannel, inputChannel)
|
||||
gameMeta := room.director.LoadMeta(game.Path)
|
||||
|
||||
go room.startVideo(gameMeta.Width, gameMeta.Height, videoEncoderType)
|
||||
var nwidth, nheight int
|
||||
if !cfg.DisableCustomSize {
|
||||
baseAspectRatio := float64(gameMeta.BaseWidth) / float64(gameMeta.Height)
|
||||
nwidth, nheight = resizeToAspect(baseAspectRatio, cfg.Width, cfg.Height)
|
||||
log.Printf("Viewport size will be changed from %dx%d (%f) -> %dx%d", cfg.Width, cfg.Height,
|
||||
baseAspectRatio, nwidth, nheight)
|
||||
} else {
|
||||
log.Println("Viewport custom size is disabled, base size will be used instead")
|
||||
nwidth, nheight = gameMeta.BaseWidth, gameMeta.BaseHeight
|
||||
}
|
||||
|
||||
if cfg.Scale > 1 {
|
||||
nwidth, nheight = nwidth*cfg.Scale, nheight*cfg.Scale
|
||||
log.Printf("Viewport size has scaled to %dx%d", nwidth, nheight)
|
||||
}
|
||||
|
||||
room.director.SetViewport(nwidth, nheight)
|
||||
|
||||
log.Println("meta: ", gameMeta)
|
||||
|
||||
go room.startVideo(nwidth, nheight, videoEncoderType)
|
||||
go room.startAudio(gameMeta.AudioSampleRate)
|
||||
room.director.Start()
|
||||
|
||||
|
|
@ -114,6 +139,17 @@ func NewRoom(roomID string, gameName string, videoEncoderType string, onlineStor
|
|||
return room
|
||||
}
|
||||
|
||||
func resizeToAspect(ratio float64, sw int, sh int) (dw int, dh int) {
|
||||
// ratio is always > 0
|
||||
dw = int(math.Round(float64(sh)*ratio/2) * 2)
|
||||
dh = sh
|
||||
if dw > sw {
|
||||
dw = sw
|
||||
dh = int(math.Round(float64(sw)/ratio/2) * 2)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// create director
|
||||
func getEmulator(emuName string, roomID string, imageChannel chan<- *image.RGBA, audioChannel chan<- []int16, inputChannel <-chan int) emulator.CloudEmulator {
|
||||
nanoarch.Init(emuName, roomID, imageChannel, audioChannel, inputChannel)
|
||||
|
|
@ -123,7 +159,7 @@ func getEmulator(emuName string, roomID string, imageChannel chan<- *image.RGBA,
|
|||
|
||||
// getGameNameFromRoomID parse roomID to get roomID and gameName
|
||||
func getGameNameFromRoomID(roomID string) string {
|
||||
parts := strings.Split(roomID, "|")
|
||||
parts := strings.Split(roomID, separator)
|
||||
if len(parts) <= 1 {
|
||||
return ""
|
||||
}
|
||||
|
|
@ -134,7 +170,7 @@ func getGameNameFromRoomID(roomID string) string {
|
|||
func generateRoomID(gameName string) string {
|
||||
// RoomID contains random number + gameName
|
||||
// Next time when we only get roomID, we can launch game based on gameName
|
||||
roomID := strconv.FormatInt(rand.Int63(), 16) + "|" + gameName
|
||||
roomID := strconv.FormatInt(rand.Int63(), 16) + separator + gameName
|
||||
return roomID
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue