Skip to content
This repository has been archived by the owner on Dec 28, 2020. It is now read-only.

Commit

Permalink
feat: Add wasm skeleton
Browse files Browse the repository at this point in the history
  • Loading branch information
bokuweb committed Aug 11, 2019
1 parent c581084 commit 946a675
Show file tree
Hide file tree
Showing 12 changed files with 815 additions and 108 deletions.
4 changes: 3 additions & 1 deletion .gitignore
@@ -1,4 +1,6 @@
reg.json
logs/*.log
*.sym
logs
logs
public/main.wasm
gopher-boy
2 changes: 2 additions & 0 deletions cmd/gopher-boy/main.go
@@ -1,3 +1,5 @@
// +build native

package main

import (
Expand Down
70 changes: 70 additions & 0 deletions cmd/gopher-boy/wasm_main.go
@@ -0,0 +1,70 @@
// +build wasm

package main

import (
"errors"
// "image/color"
"log"
"syscall/js"

"github.com/bokuweb/gopher-boy/pkg/interrupt"
"github.com/bokuweb/gopher-boy/pkg/logger"
"github.com/bokuweb/gopher-boy/pkg/pad"
"github.com/bokuweb/gopher-boy/pkg/window"

"github.com/bokuweb/gopher-boy/pkg/gpu"
"github.com/bokuweb/gopher-boy/pkg/timer"

"github.com/bokuweb/gopher-boy/pkg/cpu"
"github.com/bokuweb/gopher-boy/pkg/gb"
"github.com/bokuweb/gopher-boy/pkg/ram"

"github.com/bokuweb/gopher-boy/pkg/bus"
"github.com/bokuweb/gopher-boy/pkg/cartridge"
)

func newGB(this js.Value, args []js.Value) interface{} {
buf := []byte{}
for i := 0; i < args[0].Get("length").Int(); i++ {
buf = append(buf, byte(args[0].Index(i).Int()))
}
l := logger.NewLogger(logger.LogLevel("INFO"))
cart, err := cartridge.NewCartridge(buf)
if err != nil {
log.Fatalf("ERROR: %v", errors.New("Failed to create cartridge"))
}
vRAM := ram.NewRAM(0x2000)
wRAM := ram.NewRAM(0x2000)
hRAM := ram.NewRAM(0x80)
oamRAM := ram.NewRAM(0xA0)
gpu := gpu.NewGPU()
t := timer.NewTimer()
pad := pad.NewPad()
irq := interrupt.NewInterrupt()
b := bus.NewBus(l, cart, gpu, vRAM, wRAM, hRAM, oamRAM, t, irq, pad)
gpu.Init(b, irq)

win := window.NewWindow(pad)
emu := gb.NewGB(cpu.NewCPU(l, b, irq), gpu, t, irq, win)

this.Set("next", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
img := emu.Next()
// buf := []byte{}
// for _, color := range img {
// buf = append(buf, color.R)
// buf = append(buf, color.G)
// buf = append(buf, color.B)
// buf = append(buf, 255)
// }
return js.TypedArrayOf(img)
}))
return this
}

func main() {
w := js.Global()

w.Set("GB", js.FuncOf(newGB))
select {}
}
19 changes: 17 additions & 2 deletions makefile
@@ -1,8 +1,23 @@
.PHONY: all clean

test:
GO111MODULE=on go test github.com/bokuweb/gopher-boy/...
GO111MODULE=on go test -tags=native github.com/bokuweb/gopher-boy/...

reg:
reg-cli ./test/actual ./test/expect ./test/diff

reg-update:
reg-cli ./test/actual ./test/expect ./test/diff -U
reg-cli ./test/actual ./test/expect ./test/diff -U

build:
GO111MODULE=on go build -tags="native" -o "gopher-boy" "cmd/gopher-boy/main.go"

build-wasm:
GOOS=js GOARCH=wasm go build -tags=wasm -o "public/main.wasm" "cmd/gopher-boy/wasm_main.go"

serve:
xdg-open 'http://localhost:6008'
serve -a :5002 || (go get -v github.com/mattn/serve && serve -a :6008)

clean:
rm -f *.wasm
15 changes: 12 additions & 3 deletions pkg/gb/gb.go
Expand Up @@ -2,13 +2,14 @@ package gb

import (
"time"
"image/color"

"github.com/bokuweb/gopher-boy/pkg/cpu"
"github.com/bokuweb/gopher-boy/pkg/gpu"
"github.com/bokuweb/gopher-boy/pkg/interfaces/window"
"github.com/bokuweb/gopher-boy/pkg/interrupt"
"github.com/bokuweb/gopher-boy/pkg/timer"
"github.com/bokuweb/gopher-boy/pkg/types"
"github.com/bokuweb/gopher-boy/pkg/constants"
)

// CyclesPerFrame is cpu clock num for 1frame.
Expand Down Expand Up @@ -42,12 +43,20 @@ func (g *GB) Start() {
for {
select {
case <-t.C:
g.win.Render(g.next())
buf := g.Next()
imgData := make([]color.RGBA, constants.ScreenWidth*constants.ScreenHeight)
i := 0
for i * 4 < len(buf) {
y := constants.ScreenHeight-(i/constants.ScreenWidth)-1
imgData[y*constants.ScreenWidth+i%constants.ScreenWidth] = color.RGBA{buf[i*4], buf[i*4+1], buf[i*4+2], buf[i*4+3]}
i++
}
g.win.Render(imgData)
}
}
t.Stop()
}
func (g *GB) next() types.ImageData {
func (g *GB) Next() []byte {
for {
var cycles uint
if g.gpu.DMAStarted() {
Expand Down
2 changes: 1 addition & 1 deletion pkg/gb/gb_test.go
Expand Up @@ -73,7 +73,7 @@ func set(img *image.RGBA, imageData types.ImageData) {
func skipFrame(emu *GB, n int) types.ImageData {
var image types.ImageData
for i := 0; i < n; i++ {
image = emu.next()
image = emu.Next()
}
return image
}
Expand Down
30 changes: 23 additions & 7 deletions pkg/gpu/gpu.go
Expand Up @@ -22,7 +22,7 @@ const spriteNum = 40
type GPU struct {
bus bus.Accessor
irq interrupt.Interrupt
imageData types.ImageData
imageData []byte
mode GPUMode
clock uint
lcdc byte
Expand Down Expand Up @@ -104,7 +104,7 @@ const (
// NewGPU is GPU constructor
func NewGPU() *GPU {
return &GPU{
imageData: make([]color.RGBA, constants.ScreenWidth*constants.ScreenHeight),
imageData: make([]byte, constants.ScreenWidth*constants.ScreenHeight*4),
mode: HBlankMode,
clock: 0,
lcdc: 0x91,
Expand Down Expand Up @@ -278,7 +278,7 @@ func (g *GPU) Write(addr types.Word, data byte) {
}

// GetImageData is image data getter
func (g *GPU) GetImageData() types.ImageData {
func (g *GPU) GetImageData() []byte {
return g.imageData
}

Expand Down Expand Up @@ -328,7 +328,12 @@ func (g *GPU) buildSprites() {
c = (g.objPalette0 >> (paletteID * 2)) & 0x03
}
if paletteID != 0 {
g.imageData[(constants.ScreenHeight-1-uint(offsetY+adjustedY))*constants.ScreenWidth+uint(adjustedX+offsetX)] = g.getPalette(c)
rgba := g.getPalette(c)
base := (uint(offsetY+adjustedY)*constants.ScreenWidth+uint(adjustedX+offsetX))*4
g.imageData[base] = rgba.R
g.imageData[base+1] = rgba.G
g.imageData[base+2] = rgba.B
g.imageData[base+3] = rgba.A
}
}
}
Expand All @@ -341,7 +346,12 @@ func (g *GPU) buildBGTile() {
tileY := ((g.ly + uint(g.scrollY)) % 0x100) / 8 * 32
tileID = g.getTileID(tileY, uint(x+int(g.scrollX))/8%32, g.getBGTilemapAddr())
paletteID := g.getBGPaletteID(tileID, int(g.scrollX%8)+x, (g.ly+uint(g.scrollY))%8)
g.imageData[(constants.ScreenHeight-1-(g.ly))*constants.ScreenWidth+uint(x)] = g.getBGPalette(uint(paletteID))
rgba := g.getBGPalette(uint(paletteID))
base := ((g.ly)*constants.ScreenWidth+uint(x))*4
g.imageData[base] = rgba.R
g.imageData[base+1] = rgba.G
g.imageData[base+2] = rgba.B
g.imageData[base+3] = rgba.A
}
}

Expand All @@ -361,8 +371,14 @@ func (g *GPU) buildWindowTile() {
tileY := (g.ly - uint(g.windowY)) / 8 * 32
tileID = g.getTileID(tileY, uint(x-int(offsetX))/8, g.getWindowTilemapAddr())
paletteID := g.getBGPaletteID(tileID, int(x-int(offsetX)), (g.ly-uint(g.windowY))%8)
g.imageData[(constants.ScreenHeight-1-(g.ly))*constants.ScreenWidth+uint(x)] = g.getBGPalette(uint(paletteID))
}

rgba := g.getBGPalette(uint(paletteID))
base := ((g.ly)*constants.ScreenWidth+uint(x))*4
g.imageData[base] = rgba.R
g.imageData[base+1] = rgba.G
g.imageData[base+2] = rgba.B
g.imageData[base+3] = rgba.A
}
}

func (g *GPU) tileData0Selected() bool {
Expand Down
98 changes: 98 additions & 0 deletions pkg/window/native.go
@@ -0,0 +1,98 @@
// +build native

package window

import (
"image/color"
"math"

"github.com/bokuweb/gopher-boy/pkg/constants"
"github.com/bokuweb/gopher-boy/pkg/pad"
"github.com/bokuweb/gopher-boy/pkg/types"
"github.com/faiface/pixel"
"github.com/faiface/pixel/pixelgl"
"golang.org/x/image/colornames"
)

// Window is
type Window struct {
win *pixelgl.Window
image *pixel.PictureData
pad *pad.Pad
}


// Render renders the pixels on the window.
func (w *Window) Render(imageData types.ImageData) {
w.image.Pix = imageData

bg := color.RGBA{R: 0x0F, G: 0x38, B: 0x0F, A: 0xFF}
w.win.Clear(bg)

spr := pixel.NewSprite(pixel.Picture(w.image), pixel.R(0, 0, constants.ScreenWidth, constants.ScreenHeight))
spr.Draw(w.win, pixel.IM)
w.updateCamera()
w.win.Update()
}

func (w *Window) Run(f func()) {
pixelgl.Run(f)
}

func (w *Window) updateCamera() {
xScale := w.win.Bounds().W() / constants.ScreenWidth
yScale := w.win.Bounds().H() / constants.ScreenHeight
scale := math.Min(yScale, xScale)

shift := w.win.Bounds().Size().Scaled(0.5).Sub(pixel.ZV)
cam := pixel.IM.Scaled(pixel.ZV, scale).Moved(shift)
w.win.SetMatrix(cam)
}

func (w *Window) Init() {
cfg := pixelgl.WindowConfig{
Title: "gopher-boy",
Bounds: pixel.R(0, 0, constants.ScreenWidth, constants.ScreenHeight),
// VSync: true,
}
win, err := pixelgl.NewWindow(cfg)
if err != nil {
panic(err)
}
win.Clear(colornames.Skyblue)
w.win = win
w.image = &pixel.PictureData{
Pix: make([]color.RGBA, constants.ScreenWidth*constants.ScreenHeight),
Stride: constants.ScreenWidth,
Rect: pixel.R(0, 0, constants.ScreenWidth, constants.ScreenHeight),
}

// Hack: https://github.com/faiface/pixel/issues/140
pos := win.GetPos()
win.SetPos(pixel.ZV)
win.SetPos(pos)
w.updateCamera()
win.Update()
}

func (w *Window) PollKey() {
for key, button := range keyMap {
if w.win.JustPressed(key) {
w.pad.Press(button)
}
if w.win.JustReleased(key) {
w.pad.Release(button)
}
}
}

var keyMap = map[pixelgl.Button]pad.Button{
pixelgl.KeyZ: pad.A,
pixelgl.KeyX: pad.B,
pixelgl.KeyBackspace: pad.Select,
pixelgl.KeyEnter: pad.Start,
pixelgl.KeyRight: pad.Right,
pixelgl.KeyLeft: pad.Left,
pixelgl.KeyUp: pad.Up,
pixelgl.KeyDown: pad.Down,
}
45 changes: 45 additions & 0 deletions pkg/window/wasm.go
@@ -0,0 +1,45 @@
// +build wasm

package window

import (
"github.com/bokuweb/gopher-boy/pkg/pad"
"github.com/bokuweb/gopher-boy/pkg/types"
)

// Window is
type Window struct {
pad *pad.Pad
}

// Render renders the pixels on the window.
func (w *Window) Render(imageData types.ImageData) {
}

func (w *Window) Run(f func()) {
}

func (w *Window) Init() {
}

func (w *Window) PollKey() {
// for key, button := range keyMap {
// if w.win.JustPressed(key) {
// w.pad.Press(button)
// }
// if w.win.JustReleased(key) {
// w.pad.Release(button)
// }
// }
}

// var keyMap = map[pixelgl.Button]pad.Button{
// pixelgl.KeyZ: pad.A,
// pixelgl.KeyX: pad.B,
// pixelgl.KeyBackspace: pad.Select,
// pixelgl.KeyEnter: pad.Start,
// pixelgl.KeyRight: pad.Right,
// pixelgl.KeyLeft: pad.Left,
// pixelgl.KeyUp: pad.Up,
// pixelgl.KeyDown: pad.Down,
// }

0 comments on commit 946a675

Please sign in to comment.