Skip to content

Commit

Permalink
WASM: Avoid frame copy, pause when not in focus
Browse files Browse the repository at this point in the history
  • Loading branch information
maxpoletaev committed Jan 8, 2025
1 parent dd7f6e9 commit 7f045f9
Show file tree
Hide file tree
Showing 2 changed files with 38 additions and 22 deletions.
24 changes: 12 additions & 12 deletions cmd/dendy-wasm/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const (
//go:embed nestest.nes
var bootROM []byte

func createSystem(joy *input.Joystick, romData []byte) (*system.System, error) {
func create(joy *input.Joystick, romData []byte) (*system.System, error) {
rom, err := ines.NewFromBuffer(romData)
if err != nil {
return nil, fmt.Errorf("failed to load ROM: %v", err)
Expand All @@ -38,10 +38,10 @@ func createSystem(joy *input.Joystick, romData []byte) (*system.System, error) {
}

func main() {
log.SetFlags(0)
log.SetFlags(0) // disable timestamps
joystick := input.NewJoystick()

nes, err := createSystem(joystick, bootROM)
nes, err := create(joystick, bootROM)
if err != nil {
log.Fatalf("[ERROR] failed to initialize: %v", err)
}
Expand All @@ -53,43 +53,43 @@ func main() {
)

js.Global().Set("runFrame", js.FuncOf(func(this js.Value, args []js.Value) any {
buttons := args[0].Int()
start := time.Now()
frameBuf := args[0]
buttons := args[1].Int()
var framePtr uintptr

for {
nes.Tick()
if nes.FrameReady() {
frame := nes.Frame()
frameBytes := unsafe.Slice((*byte)(unsafe.Pointer(&frame[0])), len(frame)*4)
js.CopyBytesToJS(frameBuf, frameBytes) // TODO: can we avoid copying here?
joystick.SetButtons(uint8(buttons))
framePtr = uintptr(unsafe.Pointer(&frame[0]))
break
}
}

if debugFrameTime {
frameTimeSum += time.Since(start)
frameTime := time.Since(start)
frameTimeSum += frameTime
frameCount++

if frameCount%120 == 0 {
runtime.ReadMemStats(&mem)
elapsed := frameTimeSum / time.Duration(frameCount)
log.Printf("[DEBUG] frame time: %s, memory: %d", elapsed, mem.Alloc)
avgFrameTime := frameTimeSum / time.Duration(frameCount)
log.Printf("[INFO] frame time: %v, memory: %d", avgFrameTime, mem.HeapAlloc)
frameTimeSum = 0
frameCount = 0
}
}

return nil
return framePtr
}))

js.Global().Set("uploadROM", js.FuncOf(func(this js.Value, args []js.Value) any {
data := js.Global().Get("Uint8Array").New(args[0])
romData := make([]byte, data.Length())
js.CopyBytesToGo(romData, data)

nes2, err := createSystem(joystick, romData)
nes2, err := create(joystick, romData)
if err != nil {
log.Printf("[ERROR] failed to initialize: %v", err)
return false
Expand Down
36 changes: 26 additions & 10 deletions web/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,9 @@ const documentReady = new Promise((resolve) => {
}
});

let wasm = null;
const go = new Go();
const wasmReady = WebAssembly.instantiateStreaming(fetch("dendy.wasm"), go.importObject).then((result) => {
wasm = result.instance;
go.run(wasm);
go.run(result.instance);
});

Promise.all([wasmReady, documentReady]).then(() => {
Expand All @@ -28,8 +26,6 @@ Promise.all([wasmReady, documentReady]).then(() => {

let ctx = canvas.getContext("2d");
ctx.imageSmoothingEnabled = false;

let imageData = ctx.createImageData(width, height);
let buttonsPressed = 0;

const BUTTON_A = 1 << 0;
Expand Down Expand Up @@ -90,8 +86,28 @@ Promise.all([wasmReady, documentReady]).then(() => {
});
}

setInterval(() => {
runFrame(imageData.data, buttonsPressed);
ctx.putImageData(imageData, 0, 0);
}, 1000 / targetFPS);
});
function isInFocus() {
return document.hasFocus() && document.visibilityState === "visible";
}

function gameLoop() {
let nextFrame = () => {
let start = performance.now();

if (isInFocus()) {
let framePtr = runFrame(buttonsPressed);
let memPtr = go._inst.exports.mem?.buffer || go._inst.exports.memory.buffer; // latter is for TinyGo
let image = new ImageData(new Uint8ClampedArray(memPtr, framePtr, width * height * 4), width, height);
ctx.putImageData(image, 0, 0);
}

let elapsed = performance.now() - start;
let nextTimeout = Math.max(0, (1000 / targetFPS) - elapsed);
setTimeout(nextFrame, nextTimeout);
};

nextFrame();
}

gameLoop();
});

0 comments on commit 7f045f9

Please sign in to comment.