Upgrade to Pro — share decks privately, control downloads, hide ads and more …

gocon2019.pdf

bokuweb
October 28, 2019

 gocon2019.pdf

bokuweb

October 28, 2019
Tweet

More Decks by bokuweb

Other Decks in Programming

Transcript

  1. (P$POGFSFODF5PLZP
    (PͰͭ͘Δ(BNF#PZΤϛϡϨʔλ
    ·ͨ͸ϒϥ΢βͰͷಈ͔͠ํ
    !CPLVXFC

    View Slide

  2. !CPLVXFC
    IUUQCMPHCPLVXFCNF
    w 8FCϑϩϯτΤϯυΤϯδχΞ
    w ૊ΈࠐΈϋʔυ΢ΣΞΤϯδχΞ
    w ࢁʹੜଉ
    CPLVXFC
    ࣗݾ঺հ

    View Slide

  3. ΰʔϧ
    ΤϛϡϨʔλͷ࣮૷ͷجຊ΍
    8FC"TTFNCMZ΁ͷม׵ํ๏
    Λ஌Δ͜ͱͰָͦ͠͏ͩͳͱ
    ࢥͬͯ΋Β͑Δͱخ͍͠Ͱ͢

    View Slide

  4. %&.0
    IUUQTCPLVXFCHJUIVCJPHPQIFSCPZ

    View Slide

  5. جຊεϖοΫ
    ߲໨ ֓ཁ
    CPU LR35902 4.19MHz 8bit
    RAM 8kB
    VRAM 8kB
    ROM 256k~32MBit
    Display 4֊ௐϞϊΫϩɺ160×144υοτ
    εϓϥΠτ 8×8 ࠷େ40ݸදࣔ / 1ϥΠϯ্ʹ ࠷େ10ݸදࣔ
    എܠ 256×256υοτ
    α΢ϯυ ۣܗ೾2ch+೾ܗϝϞϦԻݯ1ch+ϊΠζ1ch
    ௨৴ϙʔτ γϦΞϧ௨৴ϙʔτ౥ࡌ
    ׂࠐΈػೳ
    ύουೖྗׂࠐΈɺγϦΞϧ௨৴ׂࠐΈɺλΠϚʔׂࠐΈ
    LCDCׂࠐΈɺVblankׂࠐΈ

    View Slide

  6. -$%
    Y

    (16
    "16 $16
    73".
    L#
    83".
    L#
    $BSUSJEHF
    CJUόε
    30. 3".
    ϒϩοΫਤ
    -3
    .)[

    View Slide

  7. $16

    View Slide

  8. Ϩδελ
    CJU CJU ֓ཁ
    " ' ൚༻ϑϥάϨδελ
    # $ ൚༻Ϩδελ
    % & ʏ
    ) - ʏ
    41 ελοΫϙΠϯλ
    1$ ϓϩάϥϜΧ΢ϯλ

    View Slide

  9. ϑϥάʢ'ʣϨδελ
    CJU ུশ ໊শ ৄࡉ
    ; θϩ ԋࢉ݁Ռ͕̌ͳΒ̍
    / αϒτϥΫτ Ճࢉ໋ྩͳΒ̌ݮࢉ໋ྩͳΒ̍
    ) ϋʔϑΩϟϦʔ
    ԼҐ̐ϏοτͰ͔Β܁Γ্͛
    ্Ґ̐Ϗοτ͔Β܁ΓԼ͕͛ൃੜͨ͠
    Β̍
    $ ΩϟϦʔ ԋࢉͰΦʔόʔϑϩʔ͕ൃੜͨ͠Β̍
    ະ࢖༻

    View Slide

  10. Ϩδελ
    type Registers struct {
    A byte
    B byte
    C byte
    D byte
    E byte
    H byte
    L byte
    F byte
    }

    View Slide

  11. Ϩδελ
    type CPU struct {
    PC types.Word
    SP types.Word
    Regs Registers
    …omitted…
    }

    View Slide

  12. ϑΣον
    σίʔυ
    ࣮ߦ
    $16ͷجຊಈ࡞

    View Slide

  13. $16ͷجຊಈ࡞
    func (cpu *CPU) Step() Cycle {
    opcode := cpu.fetch()
    inst := instructions[opcode]
    operands := cpu.fetchOperands(
    inst.OperandsSize
    )
    inst.Execute(cpu, operands)
    return inst.Cycles
    }

    View Slide

  14. $16ͷجຊಈ࡞
    func (cpu *CPU) Step() Cycle {
    opcode := cpu.fetch()
    inst := instructions[opcode]
    operands := cpu.fetchOperands(
    inst.OperandsSize
    )
    inst.Execute(cpu, operands)
    return inst.Cycles
    }

    View Slide

  15. ϑΣον
    func (cpu *CPU) fetch() byte {
    d := cpu.bus.ReadByte(cpu.PC)
    cpu.PC++
    return d
    }

    View Slide

  16. ϑΣον
    func (cpu *CPU) fetch() byte {
    d := cpu.bus.ReadByte(cpu.PC)
    cpu.PC++
    return d
    }
    1$ʹ֨ೲ͞ΕͨΞυϨε
    ͔ΒσʔλΛऔಘ

    View Slide

  17. Y
    Y
    Y
    Y
    Y''
    1$ ϓϩάϥϜΧ΢ϯλ

    ʜ
    Y
    -%# O

    Y"
    ʜ
    ϝϞϦ
    ϑΣον
    1$Yͷ৔߹

    View Slide

  18. ϑΣον
    func (cpu *CPU) fetch() byte {
    d := cpu.bus.ReadByte(cpu.PC)
    cpu.PC++
    return d
    }

    View Slide

  19. 1$ ϓϩάϥϜΧ΢ϯλ

    ϑΣον
    Y
    Y
    Y
    Y
    Y'' ʜ
    Y
    -%# O

    Y"
    ʜ
    ϝϞϦ

    View Slide

  20. σίʔυ
    func (cpu *CPU) Step() Cycle {
    opcode := cpu.fetch()
    inst := instructions[opcode]
    operands := cpu.fetchOperands(
    inst.OperandsSize
    )
    inst.Execute(cpu, operands)
    return inst.Cycles
    }

    View Slide

  21. σίʔυ
    type inst struct {
    Opcode byte //ɹ໋ྩίʔυɹ
    Description string // ໋ྩ໊
    OperandsSize uint // Ҿ਺ͷ਺
    Cycles uint // ࣮ߦʹඞཁͳαΠΫϧ਺
    Execute func(cpu *CPU, operands []byte)
    }

    View Slide

  22. σίʔυ
    var instructions = []*inst{
    &inst{0x0, "NOP", 0, 1,
    func(cpu *CPU, operands []byte) {
    ɹɹ cpu.nop()
    }
    },
    &inst{0x1, "LD BC,nn", 2, 3,
    func(cpu *CPU, operands []byte) {
    cpu.ldn_nn(…)
    }
    },
    …omitted…
    &inst{0x6, "LD B,n", 1, 2,
    func(cpu *CPU, operands []byte) {
    cpu.ldnn_n(&cpu.Regs.B, operands)
    }
    },
    …omitted…
    }

    View Slide

  23. σίʔυ
    var instructions = []*inst{
    &inst{0x0, "NOP", 0, 1,
    func(cpu *CPU, operands []byte) {
    ɹɹ cpu.nop()
    }
    },
    &inst{0x1, "LD BC,nn", 2, 3,
    func(cpu *CPU, operands []byte) {
    cpu.ldn_nn(…)
    }
    },
    …omitted…
    &inst{0x6, "LD B,n", 1, 2,
    func(cpu *CPU, operands []byte) {
    cpu.ldnn_n(&cpu.Regs.B, operands)
    }
    },
    …omitted…
    }
    -%# O
    Φϖϥϯυ਺
    $16αΠΫϧ਺
    [email protected]ؔ਺

    View Slide

  24. σίʔυ
    func (cpu *CPU) Step() Cycle {
    opcode := cpu.fetch()
    inst := instructions[opcode]
    operands := cpu.fetchOperands(
    inst.OperandsSize
    )
    inst.Execute(cpu, operands)
    return inst.Cycles
    }

    View Slide

  25. σίʔυ
    func (cpu *CPU) fetchOperands(size uint) []byte {
    operands := []byte{}
    for i := 0; i < int(size); i++ {
    operands = append(operands, cpu.fetch())
    }
    return operands
    }

    View Slide

  26. 1$ ϓϩάϥϜΧ΢ϯλ

    Y
    Y
    Y
    Y
    Y'' ʜ
    Y
    -%# O

    Y"
    ʜ
    ϝϞϦ
    σίʔυ
    Φϖϥϯυଈ஋Y"
    ͕Ϧʔυ͞ΕΔ

    View Slide

  27. ࣮ߦ
    func (cpu *CPU) Step() Cycle {
    opcode := cpu.fetch()
    inst := instructions[opcode]
    operands := cpu.fetchOperands(
    inst.OperandsSize
    )
    inst.Execute(cpu, operands)
    return inst.Cycles
    }

    View Slide

  28. ࣮ߦ
    // LD nn,n
    // Description:
    // Put value nn into n.
    // Use with:
    // nn = B,C,D,E,H,L,BC,DE,HL,SP
    func (cpu *CPU) ldnn_n(reg *types.Register,
    operands []byte) {
    *reg = operands[0]
    }

    View Slide

  29. #VT.FNPSZ

    View Slide

  30. ϝϞϦϚοϓ
    L#30.CBOL
    L#TXJUDIBCMF30.
    L#73".
    L#TXJUDIBCMF3".
    L#83".
    83".NJSSPS
    0". 4QSJUF
    3".
    QFSJQIFSBM
    *OUFSOBM3".
    *32&OBCMF3FHJTUFS
    Y
    Y
    Y
    Y"
    Y$
    Y&
    Y'&
    Y'&"
    Y''
    Y''''

    View Slide

  31. ϝϞϦϚοϓ
    L#30.CBOL
    L#TXJUDIBCMF30.
    L#73".
    L#TXJUDIBCMF3".
    L#83".
    83".NJSSPS
    0". 4QSJUF
    3".
    QFSJQIFSBM
    *OUFSOBM3".
    *32&OBCMF3FHJTUFS
    Y
    Y
    Y
    Y"
    Y$
    Y&
    Y'&
    Y'&"
    Y''
    Y''''
    ΧʔτϦοδ಺෦
    ͷϝϞϦ

    View Slide

  32. Ϧʔυ
    func (b *Bus) ReadByte(addr types.Word) byte {
    switch {
    case addr >= 0x0000 && addr <= 0x7FFF:
    if b.bootmode && addr < 0x0100 {
    return BIOS[addr]
    }
    return b.cartridge.ReadByte(addr)
    // Video RAM
    case addr >= 0x8000 && addr <= 0x9FFF:
    return b.vRAM.Read(addr - 0x8000)
    case addr >= 0xA000 && addr <= 0xBFFF:
    return b.cartridge.ReadByte(addr)
    // Working RAM
    case addr >= 0xC000 && addr <= 0xDFFF:
    return b.wRAM.Read(addr - 0xC000)
    case … omitted …
    default:
    }
    return 0
    }

    View Slide

  33. (16

    View Slide

  34. MJOFQY
    ࠨ͔Βӈʹ
    ඳը͍ͯ͘͠
    λΠϛϯά
    -$%

    View Slide

  35. -$%
    λΠϛϯά
    MJOFQY
    ը໘ӈ୺·Ͱ౸ୡͨ͠Β
    ࣍ͷϥΠϯͷࠨ୺ʹ໭Δ
    ʢ)CMBOL

    ͜ͷظؒΛؚΊΔͱ
    MJOFDMPDL

    View Slide

  36. -$%
    λΠϛϯά
    MJOFQY
    MJOF

    View Slide

  37. ࣍ͷඳըͷͨΊʹઌ಄΁໭Δ
    ͜ͷͨΊͷظؒ7#MBOL
    MJOF෼ͷ࣌ؒ
    -$%
    λΠϛϯά
    MJOFQY
    ϑϨʔϜ͸
    DMPDL DMPDL

    View Slide

  38. (16ͷجຊಈ࡞
    func (g *GPU) Step(cycles uint) {
    …omitted…
    g.clock += cycles
    if g.clock >= 456 {
    if g.ly < 144 {
    g.buildBGTile()
    } else if g.ly == 144 {
    g.buildSprites()
    } else if g.ly >= 144+10 {
    g.ly = 0
    …omitted…
    }
    g.ly++
    g.clock -= 456
    }
    }

    View Slide

  39. (16ͷجຊಈ࡞
    func (g *GPU) Step(cycles uint) {
    …omitted…
    g.clock += cycles
    if g.clock >= 456 {
    if g.ly < 144 {
    g.buildBGTile()
    } else if g.ly == 144 {
    g.buildSprites()
    } else if g.ly >= 144+10 {
    g.ly = 0
    …omitted…
    }
    g.ly++
    g.clock -= 456
    }
    }
    (16αΠΫϧ਺

    View Slide

  40. func (g *GPU) Step(cycles uint) {
    …omitted…
    g.clock += cycles
    if g.clock >= 456 {
    if g.ly < 144 {
    g.buildBGTile()
    } else if g.ly == 144 {
    g.buildSprites()
    } else if g.ly >= 144+10 {
    g.ly = 0
    …omitted…
    }
    g.ly++
    g.clock -= 456
    }
    }
    (16ͷجຊಈ࡞
    ΫϩοΫΛMJOF෼
    Χ΢ϯτΞοϓ
    ඳըதϥΠϯΛࣔ͢
    ϨδελΛΠϯΫϦϝϯτ

    View Slide

  41. (16ͷجຊಈ࡞
    func (g *GPU) Step(cycles uint) {
    …omitted…
    g.clock += cycles
    if g.clock >= 456 {
    if g.ly < 144 {
    g.buildBGTile()
    } else if g.ly == 144 {
    g.buildSprites()
    } else if g.ly >= 144+10 {
    g.ly = 0
    …omitted…
    }
    g.ly++
    g.clock -= 456
    }
    }
    දࣔൣғ಺Ͱ͋Ε͹
    എܠMJOFߏங

    View Slide

  42. (16ͷجຊಈ࡞
    func (g *GPU) Step(cycles uint) {
    …omitted…
    g.clock += cycles
    if g.clock >= 456 {
    if g.ly < 144 {
    g.buildBGTile()
    } else if g.ly == 144 {
    g.buildSprites()
    } else if g.ly >= 144+10 {
    g.ly = 0
    …omitted…
    }
    g.ly++
    g.clock -= 456
    }
    }
    ࠷ऴϥΠϯͰ
    εϓϥΠτΛඳը

    View Slide

  43. (16ͷجຊಈ࡞
    func (g *GPU) Step(cycles uint) {
    …omitted…
    g.clock += cycles
    if g.clock >= 456 {
    if g.ly < 144 {
    g.buildBGTile()
    } else if g.ly == 144 {
    g.buildSprites()
    } else if g.ly >= 144+10 {
    g.ly = 0
    …omitted…
    }
    g.ly++
    g.clock -= 456
    }
    }
    7CMBOLظؒ׬ྃ

    View Slide

  44. ̔#ZUFº#ZUFͰºαΠζͷը૾Λදݱ
    "EESFTTT %BUB
    Y Y
    Y Y'
    Y Y''
    Y Y''
    Y Y''
    Y" Y&
    Y$ Y$
    Y& Y
    "EESFTTT %BUB
    Y Y
    Y Y'
    Y Y#'
    Y Y#'
    Y Y''
    Y# Y&
    Y% Y$
    Y' Y

























    എܠͷඳը
    ̌ʙ̏ͷύϨοτ*%
    ̎CJUͷ৭৘ใ

    View Slide

  45. λΠϧσʔλ
    Y
    Y''
    Y''
    Y
    λΠϧ൪߸͕̌ʙ·Ͱ
    ׂΓ౰ͯΒΕΔ

    View Slide

  46. λΠϧϚοϓ
    QY UJMF

    QY
    UJMF

    Y
    Y#''
    YdY#''Λ
    YλΠϧ෼ͷ
    λΠϧ൪߸ͰຒΊΔ
    QY UJMF

    QY
    UJMF

    දࣔՄೳൣғ
    දࣔൣғ

    View Slide

  47. λΠϧϚοϓ
    4DSPMM:
    Y''
    4DSPMM9
    Y''
    QY
    QY
    දࣔൣғ

    View Slide

  48. σʔλͷՄࢹԽ
    IUUQTCPLVXFCHJUIVCJPHPQIFSCPZEFCVHIUNM

    View Slide

  49. എܠͷඳը
    func (g *GPU) buildBGTile() {
    var tileID int
    for x := 0; x < constants.ScreenWidth; x++ {
    tileY := ((g.ly + uint(g.scrollY)) % 0x100) / 8 * 32
    tileX := x+int(g.scrollX))/8%32
    tileID = g.getTileID(
    tileY, uint(tileX, g.getBGTilemapAddr()
    )
    paletteID := g.getBGPaletteID(
    tileID, int(g.scrollX%8)+x, (g.ly+uint(g.scrollY))%8
    )
    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
    }
    }

    View Slide

  50. എܠͷඳը
    func (g *GPU) buildBGTile() {
    var tileID int
    for x := 0; x < constants.ScreenWidth; x++ {
    tileY := ((g.ly + uint(g.scrollY)) % 0x100) / 8 * 32
    tileX := x+int(g.scrollX))/8%32
    tileID = g.getTileID(
    tileY, uint(tileX, g.getBGTilemapAddr()
    )
    paletteID := g.getBGPaletteID(
    tileID, int(g.scrollX%8)+x, (g.ly+uint(g.scrollY))%8
    )
    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
    }
    }
    ϥΠϯ෼

    View Slide

  51. func (g *GPU) buildBGTile() {
    var tileID int
    for x := 0; x < constants.ScreenWidth; x++ {
    tileY := ((g.ly + uint(g.scrollY)) % 0x100) / 8 * 32
    tileX := x+int(g.scrollX))/8%32
    tileID = g.getTileID(
    tileY, uint(tileX, g.getBGTilemapAddr()
    )
    paletteID := g.getBGPaletteID(
    tileID, int(g.scrollX%8)+x, (g.ly+uint(g.scrollY))%8
    )
    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
    }
    }
    എܠͷϥΠϯඳը
    UJMFͷ9:࠲ඪ

    View Slide

  52. func (g *GPU) buildBGTile() {
    var tileID int
    for x := 0; x < constants.ScreenWidth; x++ {
    tileY := ((g.ly + uint(g.scrollY)) % 0x100) / 8 * 32
    tileX := x+int(g.scrollX))/8%32
    tileID = g.getTileID(
    tileY, uint(tileX, g.getBGTilemapAddr()
    )
    paletteID := g.getBGPaletteID(
    tileID, int(g.scrollX%8)+x, (g.ly+uint(g.scrollY))%8
    )
    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
    }
    }
    എܠͷϥΠϯඳը
    λΠϧ൪߸Λ
    73".͔Βऔಘ
    λΠϧ൪߸͔Β
    ύϨοτ൪߸Λऔಘ

    View Slide

  53. എܠͷϥΠϯඳը
    func (g *GPU) buildBGTile() {
    var tileID int
    for x := 0; x < constants.ScreenWidth; x++ {
    tileY := ((g.ly + uint(g.scrollY)) % 0x100) / 8 * 32
    tileX := x+int(g.scrollX))/8%32
    tileID = g.getTileID(
    tileY, uint(tileX, g.getBGTilemapAddr()
    )
    paletteID := g.getBGPaletteID(
    tileID, int(g.scrollX%8)+x, (g.ly+uint(g.scrollY))%8
    )
    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
    }
    }
    όοϑΝʹ3(#"
    Λ֨ೲ

    View Slide

  54. ΤϛϡϨʔλ

    View Slide

  55. func (g *GB) Next() []byte {
    for {
    cycles := g.cpu.Step() * 4
    g.gpu.Step(cycles)
    …omitted…
    g.currentCycle += cycles
    if g.currentCycle >= 70224 {
    g.win.PollKey()
    g.currentCycle -= 70224
    return g.gpu.GetImageData()
    }
    }
    }
    ΤϛϡϨʔλͷجຊಈ࡞

    View Slide

  56. func (g *GB) Next() []byte {
    for {
    cycles := g.cpu.Step() * 4
    g.gpu.Step(cycles)
    …omitted…
    g.currentCycle += cycles
    if g.currentCycle >= 70224 {
    g.win.PollKey()
    g.currentCycle -= 70224
    return g.gpu.GetImageData()
    }
    }
    }
    ΤϛϡϨʔλͷجຊಈ࡞
    $16Λ໋ྩ෼࣮ߦ
    $16ΫϩοΫ͸෼प
    ͳͷͰY

    View Slide

  57. func (g *GB) Next() []byte {
    for {
    cycles := g.cpu.Step() * 4
    g.gpu.Step(cycles)
    …omitted…
    g.currentCycle += cycles
    if g.currentCycle >= 70224 {
    g.win.PollKey()
    g.currentCycle -= 70224
    return g.gpu.GetImageData()
    }
    }
    }
    ΤϛϡϨʔλͷجຊಈ࡞
    (16ΛܦաΫϩοΫ਺෼Քಇ

    View Slide

  58. ΤϛϡϨʔλͷجຊಈ࡞
    func (g *GB) Next() []byte {
    for {
    cycles := g.cpu.Step() * 4
    g.gpu.Step(cycles)
    …omitted…
    g.currentCycle += cycles
    if g.currentCycle >= 70224 {
    g.win.PollKey()
    g.currentCycle -= 70224
    return g.gpu.GetImageData()
    }
    }
    }
    ੵࢉΫϩοΫ਺͕̍ϑϨʔϜ෼
    ௒͑ͨΒը૾σʔλΛฦ͢

    View Slide

  59. func (g *GB) Start() {
    t := time.NewTicker(16 * time.Millisecond)
    for {
    select {
    case <-t.C:
    buf := g.Next()
    g.win.Render(buf)
    }
    }
    }
    ΤϛϡϨʔλͷجຊಈ࡞
    NTपظͰը૾Λੜ੒

    View Slide

  60. ΤϛϡϨʔλͷجຊಈ࡞
    func (w *Window) Render(buf []byte) {
    imgData := make([]color.RGBA, constants.ScreenWidth*constants.ScreenHeight)
    i := 0
    for i*4 < len(buf) {
    y := constants.ScreenHeight - (i / constants.ScreenWidth) - 1
    d := color.RGBA{buf[i*4], buf[i*4+1], buf[i*4+2], buf[i*4+3]
    imgData[y*constants.ScreenWidth+i%constants.ScreenWidth] = d
    i++
    }
    w.image.Pix = imgData
    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()
    }

    View Slide

  61. 5FTUJOH

    View Slide

  62. ΤϛϡϨʔλͷςετ

    View Slide

  63. t.Run(tt.name, func(t *testing.T) {
    emu := setup(“hello.gb”)
    buf := skipFrame(emu, 100)
    file, err := os.Create(“hello” + ".png")
    defer file.Close()
    …omitted…
    img := image.NewRGBA(image.Rect(0, 0, 160, 144))
    set(img, buf)
    if err := png.Encode(file, img); err != nil {
    …omitted…
    }
    })
    ΤϛϡϨʔλͷςετ

    View Slide

  64. t.Run(tt.name, func(t *testing.T) {
    emu := setup(“hello.gb”)
    buf := skipFrame(emu, 100)
    file, err := os.Create(“hello” + ".png")
    defer file.Close()
    …omitted…
    img := image.NewRGBA(image.Rect(0, 0, 160, 144))
    set(img, buf)
    if err := png.Encode(file, img); err != nil {
    …omitted…
    }
    })
    ΤϛϡϨʔλͷςετ
    ೚ҙͷϑϨʔϜͷը૾Λ
    औಘͰ͖ΔΑ͏ʹ͓ͯ͘͠

    View Slide

  65. t.Run(tt.name, func(t *testing.T) {
    emu := setup(“hello.gb”)
    buf := skipFrame(emu, 100)
    file, err := os.Create(“hello” + ".png")
    defer file.Close()
    …omitted…
    img := image.NewRGBA(image.Rect(0, 0, 160, 144))
    set(img, buf)
    if err := png.Encode(file, img); err != nil {
    …omitted…
    }
    })
    ΤϛϡϨʔλͷςετ
    QOHͱͯ͠อଘ

    View Slide

  66. ΤϛϡϨʔλͷςετ
    reg-cli ./test/actual ./test/expect ./test/diff -R ./report.html
    SFHWJ[SFHDMJΛ࢖ͬͯ
    7JTVBM3FHSFTTJPO5FTU

    View Slide

  67. ΤϛϡϨʔλͷςετ
    ը૾ʹࠩ෼͕͋ͬͨΒ$*Λམͱ͢Α͏ʹ

    View Slide

  68. 8FC"TTFNCMZ

    View Slide

  69. ɾϒϥ΢βͰಈ͘όΠφϦϑΥʔϚοτ
    ɾ͍ΖΜͳݴޠ͔ΒXBTNΛు͚Δ
    ɾαΠζ͕খ͘͞ϩʔσΟϯά࣌ؒ΋୹͍
    8FC"TTFNCMZ

    View Slide

  70. ᶃ8BTNͷॳظԽ
    ᶄ+4ଆ΁ؔ਺ΛFYQPSU
    ᶅ+4Ͱ30.σʔλΛऔಘ
    ᶆ8BTNʹ30.σʔλΛ౉͠
    ΤϛϡϨʔλ࡞੒
    ᶇSFRVFTU"OJNBUJPO'SBNF
    ͰඳըσʔλΛऔಘ͠$BOWBTʹ
    ඳը
    ϒϥ΢βͰಈ࡞ͤ͞Δઓུ

    View Slide

  71. ϒϥ΢βͰಈ࡞ͤ͞Δઓུ
    ᶃ8BTNͷॳظԽ
    ᶄ+4ଆ΁ؔ਺ΛFYQPSU
    ᶅ+4Ͱ30.σʔλΛऔಘ
    ᶆ8BTNʹ30.σʔλΛ౉͠
    ΤϛϡϨʔλ࡞੒
    ᶇSFRVFTU"OJNBUJPO'SBNF
    ͰඳըσʔλΛऔಘ͠$BOWBTʹ
    ඳը

    View Slide

  72. 8BTN

    View Slide

  73. const go = new Go();
    const res = await fetch("./main.wasm");
    const bytes = await res.arrayBuffer();
    const { instance } = await WebAssembly.instantiate(
    bytes,
    go.importObject
    );
    go.run(instance);
    8BTN+4

    View Slide

  74. const go = new Go();
    const res = await fetch("./main.wasm");
    const bytes = await res.arrayBuffer();
    const { instance } = await WebAssembly.instantiate(
    bytes,
    go.importObject
    );
    go.run(instance);
    8BTN+4
    [email protected]Ͱੜ੒͞ΕΔ
    JNQPSUΦϒδΣΫτΛ౉͢

    View Slide

  75. ᶃ8BTNͷॳظԽ
    ᶄ+4ଆ΁ؔ਺ΛFYQPSU
    ᶅ+4Ͱ30.σʔλΛऔಘ
    ᶆ8BTNʹ30.σʔλΛ౉͠
    ΤϛϡϨʔλ࡞੒
    ᶇSFRVFTU"OJNBUJPO'SBNF
    ͰඳըσʔλΛऔಘ͠$BOWBTʹ
    ඳը
    ϒϥ΢βͰಈ࡞ͤ͞Δઓུ

    View Slide

  76. func main() {
    w := js.Global()
    w.Set("GB", js.FuncOf(newGB))
    select {}
    }
    8BTN(PMBOH
    HMPCBMʹ(#Λੜ΍͢

    View Slide

  77. 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()))
    }
    cart, err := cartridge.NewCartridge(buf)
    …omitted…
    }
    8BTN(PMBOH
    +4͔ΒΧʔτϦοδͷ
    σʔλΛड͚औΔ

    View Slide

  78. func newGB(this js.Value, args []js.Value) interface{} {
    …omitted…
    this.Set("next", js.FuncOf(
    func(this js.Value, args []js.Value) interface{} {
    img := emu.Next()
    return js.TypedArrayOf(img)
    }
    ))
    …omitted…
    return this
    }
    8BTN(PMBOH

    View Slide

  79. func newGB(this js.Value, args []js.Value) interface{} {
    …omitted…
    this.Set("next", js.FuncOf(
    func(this js.Value, args []js.Value) interface{} {
    img := emu.Next()
    return js.TypedArrayOf(img)
    }
    ))
    …omitted…
    return this
    }
    8BTN(PMBOH
    OFYUͱ͍͏ؔ਺Λ+4ଆʹFYQPSU

    View Slide

  80. func newGB(this js.Value, args []js.Value) interface{} {
    …omitted…
    this.Set("next", js.FuncOf(
    func(this js.Value, args []js.Value) interface{} {
    img := emu.Next()
    return js.TypedArrayOf(img)
    }
    ))
    …omitted…
    return this
    }
    8BTN(PMBOH
    ࣍ͷϑϨʔϜΛ5ZQFE"SSBZͱͯ͠ฦ͢

    View Slide

  81. ᶃ8BTNͷॳظԽ
    ᶄ+4ଆ΁ؔ਺ΛFYQPSU
    ᶅ+4Ͱ30.σʔλΛऔಘ
    ᶆ8BTNʹ30.σʔλΛ౉͠
    ΤϛϡϨʔλ࡞੒
    ᶇSFRVFTU"OJNBUJPO'SBNF
    ͰඳըσʔλΛऔಘ͠$BOWBTʹ
    ඳը
    ϒϥ΢βͰಈ࡞ͤ͞Δઓུ

    View Slide

  82. const rom = await fetch("./tobu.gb");
    const buf = await rom.arrayBuffer();
    8BTN+4

    View Slide

  83. ᶃ8BTNͷॳظԽ
    ᶄ+4ଆ΁ؔ਺ΛFYQPSU
    ᶅ+4Ͱ30.σʔλΛऔಘ
    ᶆ8BTNʹ30.σʔλΛ౉͠
    ΤϛϡϨʔλ࡞੒
    ᶇSFRVFTU"OJNBUJPO'SBNF
    ͰඳըσʔλΛऔಘ͠$BOWBTʹ
    ඳը
    ϒϥ΢βͰಈ࡞ͤ͞Δઓུ

    View Slide

  84. let gb = new GB(new Uint8Array(buf));
    8BTN+4

    View Slide

  85. ᶃ8BTNͷॳظԽ
    ᶄ+4ଆ΁ؔ਺ΛFYQPSU
    ᶅ+4Ͱ30.σʔλΛऔಘ
    ᶆ8BTNʹ30.σʔλΛ౉͠
    ΤϛϡϨʔλ࡞੒
    ᶇSFRVFTU"OJNBUJPO'SBNF
    ͰඳըσʔλΛऔಘ͠$BOWBTʹ
    ඳը
    ϒϥ΢βͰಈ࡞ͤ͞Δઓུ

    View Slide

  86. const frame = () => {
    const img = gb.next();
    image.data.set(img);
    ctx.putImageData(image, 0, 0);
    requestAnimationFrame(frame);
    };
    frame();
    8BTN+4

    View Slide

  87. GOOS=js GOARCH=wasm go build -tags=wasm
    -oɹ“main.wasm” "gopher-boy/wasm_main.go"
    Ϗϧυ

    View Slide

  88. .#
    αΠζ
    wasm
    ,#
    wasm_exec.js

    View Slide

  89. ύϑΥʔϚϯε HP

    ϒϥ΢β ̍'SBNF NT

    $ISPNF NT
    'JSF'PY NT
    4BGBSJ NT
    MacBook Air (Retina, 13-inch, 2018)

    1.6 GHz Intel Core i5

    16 GB 2133 MHz LPDDR3

    go1.12.12

    TobuTobuGirl

    View Slide

  90. ύϑΥʔϚϯε
    19.11msec

    View Slide

  91. func (cpu *CPU) applyZeroBy(computed byte) {
    if computed == 0 {
    cpu.setFlag(Z)
    } else {
    cpu.clearFlag(Z)
    }
    }
    BQQMZ;FSP#Z HP

    View Slide

  92. 8BTNXBU
    wasm2wat main.wasm -o main.wat

    View Slide

  93. BQQMZ;FSP#Z XBU

    (func $github.com_bokuweb_gopher_boy_pkg_cpu.__CPU_.applyZeroBy (type 0) (result i32)
    loop ;; label = @1
    block ;; label = @2
    block ;; label = @3
    block ;; label = @4
    block ;; label = @5
    block ;; label = @6
    block ;; label = @7
    block ;; label = @8
    global.get 1
    br_table 0 (;@8;) 0 (;@8;) 1 (;@7;) 2 (;@6;) 3 (;@5;) 3 (;@5;) 4 (;@4;) 4 (;@4;) 5 (;@3;) 6 (;@2;)
    end
    global.get 2
    global.get 4
    i32.wrap_i64
    i32.load offset=16
    i32.le_u
    if ;; label = @8
    global.get 2
    i32.const 8
    i32.sub
    global.set 2
    global.get 2
    i64.const 390397952
    i64.store
    i32.const 0
    global.set 1
    call $runtime.morestack_noctxt
    if ;; label = @9
    i32.const 1
    return
    end
    end
    global.get 2
    i32.const 16
    i32.sub
    global.set 2
    global.get 2
    i64.load8_u offset=32
    i64.const 56
    i64.shl
    i64.const 56
    i64.shr_u
    i64.eqz
    i64.extend_i32_u
    i32.wrap_i64
    i32.eqz
    if ;; label = @8
    i32.const 6
    global.set 1
    br 7 (;@1;)
    end
    end
    global.get 2
    global.get 2
    i64.load offset=24
    i64.store
    global.get 2
    i64.const 4
    i32.const 8
    i32.sub
    global.set 2
    global.get 2
    i64.const 390397955
    i64.store
    i32.const 0
    global.set 1
    call $github.com_bokuweb_gopher_boy_pkg_cpu.__CPU_.setFlag
    if ;; label = @7
    i32.const 1
    return
    end
    end
    end
    global.get 2
    i32.const 16
    i32.add
    global.set 2
    global.get 2
    i32.load16_u offset=2
    global.set 0
    global.get 2
    i32.load16_u
    global.set 1
    global.get 2
    i32.const 8
    i32.add
    global.set 2
    i32.const 0
    return
    end
    global.get 2
    global.get 2
    i64.load offset=24
    i64.store
    global.get 2
    i64.const 4
    i64.store offset=8
    global.get 2
    i32.const 8
    i32.sub
    global.set 2
    global.get 2
    i64.const 390397960
    i64.store
    i32.const 0
    global.set 1
    call $github.com_bokuweb_gopher_boy_pkg_cpu.__CPU_.clearFlag
    if ;; label = @4
    i32.const 1
    return
    end
    end
    i32.const 4
    global.set 1
    br 1 (;@1;)
    end
    end
    unreachable)

    View Slide

  94. ͍͞͝ʹ
    ɾήʔϜϘʔΠ͕݁ߏγϯϓϧ
    ɹͳͷͰ͓͢͢Ί
    ɾ8BTN͸ݱ࣌఺ͰαΠζ΍
    ɹύϑΥʔϚϯεʹߟྀ͕ඞཁ
    ɹ UJOZHPͳͲʹظ଴

    ɾࣗ෼΋΍ͬͯΈΑ͏͔ͱ
    ɹࢥ͍͚ͬͯͨͩͨΒ޾͍Ͱ͢

    View Slide

  95. ͋Γ͕ͱ͏͍͟͝·ͨ͠✨

    View Slide