diff --git a/src/dchip8.cpp b/src/dchip8.cpp index 09f3bf8..63a3463 100644 --- a/src/dchip8.cpp +++ b/src/dchip8.cpp @@ -290,31 +290,31 @@ FILE_SCOPE Chip8Controller dchip8_controller_map_input(PlatformInput input) Chip8Controller result = {}; - result.key[0x01] = input.key_1.isDown; - result.key[0x02] = input.key_2.isDown; - result.key[0x03] = input.key_3.isDown; - result.key[0x0C] = input.key_4.isDown; + result.key[0x01] = input.key_1.endedDown; + result.key[0x02] = input.key_2.endedDown; + result.key[0x03] = input.key_3.endedDown; + result.key[0x0C] = input.key_4.endedDown; - result.key[0x04] = input.key_q.isDown; - result.key[0x05] = input.key_w.isDown; - result.key[0x06] = input.key_e.isDown; - result.key[0x0D] = input.key_r.isDown; + result.key[0x04] = input.key_q.endedDown; + result.key[0x05] = input.key_w.endedDown; + result.key[0x06] = input.key_e.endedDown; + result.key[0x0D] = input.key_r.endedDown; - result.key[0x07] = input.key_a.isDown; - result.key[0x08] = input.key_s.isDown; - result.key[0x09] = input.key_d.isDown; - result.key[0x0E] = input.key_f.isDown; + result.key[0x07] = input.key_a.endedDown; + result.key[0x08] = input.key_s.endedDown; + result.key[0x09] = input.key_d.endedDown; + result.key[0x0E] = input.key_f.endedDown; - result.key[0x0A] = input.key_z.isDown; - result.key[0x00] = input.key_x.isDown; - result.key[0x0B] = input.key_c.isDown; - result.key[0x0F] = input.key_v.isDown; + result.key[0x0A] = input.key_z.endedDown; + result.key[0x00] = input.key_x.endedDown; + result.key[0x0B] = input.key_c.endedDown; + result.key[0x0F] = input.key_v.endedDown; return result; } void dchip8_update(PlatformRenderBuffer renderBuffer, PlatformInput input, - PlatformMemory memory) + PlatformMemory memory, u32 cyclesToEmulate) { DQNT_ASSERT(cpu.indexRegister >= 0 && cpu.indexRegister <= 0xFFF); DQNT_ASSERT(cpu.programCounter >= 0 && cpu.programCounter <= 0xFFF); @@ -333,7 +333,7 @@ void dchip8_update(PlatformRenderBuffer renderBuffer, PlatformInput input, if (cpu.state == chip8state_load_file) { PlatformFile file = {}; - if (platform_open_file(L"roms/pong", &file)) + if (platform_open_file(L"roms/brix", &file)) { DQNT_ASSERT((cpu.INIT_ADDRESS + file.size) <= memory.permanentMemSize); @@ -359,7 +359,7 @@ void dchip8_update(PlatformRenderBuffer renderBuffer, PlatformInput input, if (controller.key[keyVal]) { u8 regIndex = cpu.storeKeyToRegisterIndex; - DQNT_ASSERT(keyVal >= 0 && keyVal < 0x0F); + DQNT_ASSERT(keyVal >= 0 && keyVal <= 0x0F); cpu.registerArray[regIndex] = (u8)keyVal; cpu.state = chip8state_running; @@ -370,443 +370,468 @@ void dchip8_update(PlatformRenderBuffer renderBuffer, PlatformInput input, if (cpu.state == chip8state_running) { - u8 opHighByte = mainMem[cpu.programCounter++]; - u8 opLowByte = mainMem[cpu.programCounter++]; - - u8 opFirstNibble = (opHighByte & 0xF0); - switch (opFirstNibble) + bool earlyExit = false; + for (u32 opCycle = 0; opCycle < cyclesToEmulate && !earlyExit; + opCycle++) { - case 0x00: + u8 opHighByte = mainMem[cpu.programCounter++]; + u8 opLowByte = mainMem[cpu.programCounter++]; + u8 opFirstNibble = (opHighByte & 0xF0); + switch (opFirstNibble) { - // CLS - 00E0 - Clear the display - if (opLowByte == 0xE0) + case 0x00: { - dchip8_init_display(renderBuffer); - } - // RET - 00EE - Return from subroutine - else if (opLowByte == 0xEE) - { - cpu.programCounter = cpu.stack[--cpu.stackPointer]; - } - } - break; - - case 0x10: - case 0x20: - { - u16 loc = ((0x0F & opHighByte) << 8) | opLowByte; - DQNT_ASSERT(loc <= 0x0FFF); - - // JP addr - 1nnn - Jump to location nnn - if (opFirstNibble == 0x10) - { - // NOTE: Jump to loc, as per below - } - // Call addr - 2nnn - Call subroutine at nnn - else - { - DQNT_ASSERT(opFirstNibble == 0x20); - cpu.stack[cpu.stackPointer++] = cpu.programCounter; - DQNT_ASSERT(cpu.stackPointer < DQNT_ARRAY_COUNT(cpu.stack)); - } - - cpu.programCounter = loc; - } - break; - - case 0x30: - case 0x40: - { - u8 regNum = (0x0F & opHighByte); - DQNT_ASSERT(regNum < DQNT_ARRAY_COUNT(cpu.registerArray)); - u8 *vx = &cpu.registerArray[regNum]; - - u8 valToCheck = opLowByte; - - // SE Vx, byte - 3xkk - Skip next instruction if Vx == kk - if (opFirstNibble == 0x30) - { - if (*vx == valToCheck) cpu.programCounter += 2; - } - // SNE Vx, byte - 4xkk - Skip next instruction if Vx == kk - else - { - DQNT_ASSERT(opFirstNibble == 0x40); - if (*vx != valToCheck) cpu.programCounter += 2; - } - } - break; - - // SE Vx, Vy - 5xy0 - Skip next instruction if Vx = Vy - case 0x50: - { - u8 firstRegNum = (0x0F & opHighByte); - DQNT_ASSERT(firstRegNum < DQNT_ARRAY_COUNT(cpu.registerArray)); - - u8 secondRegNum = (0xF0 & opLowByte) >> 4; - DQNT_ASSERT(secondRegNum < DQNT_ARRAY_COUNT(cpu.registerArray)); - - u8 *vx = &cpu.registerArray[firstRegNum]; - u8 *vy = &cpu.registerArray[secondRegNum]; - - if (*vx == *vy) cpu.programCounter += 2; - } - break; - - case 0x60: - case 0x70: - { - u8 regNum = (0x0F & opHighByte); - DQNT_ASSERT(regNum < DQNT_ARRAY_COUNT(cpu.registerArray)); - u8 valToOperateOn = opLowByte; - - u8 *vx = &cpu.registerArray[regNum]; - // LD Vx, byte - 6xkk - Set Vx = kk - if (opFirstNibble == 0x60) - { - *vx = valToOperateOn; - } - // ADD Vx, byte - 7xkk - Set Vx = Vx + kk - else - { - DQNT_ASSERT(opFirstNibble == 0x70); - *vx += valToOperateOn; - } - } - break; - - case 0x80: - { - u8 firstRegNum = (0x0F & opHighByte); - DQNT_ASSERT(firstRegNum < DQNT_ARRAY_COUNT(cpu.registerArray)); - - u8 secondRegNum = (0xF0 & opLowByte) >> 4; - DQNT_ASSERT(secondRegNum < DQNT_ARRAY_COUNT(cpu.registerArray)); - - u8 *vx = &cpu.registerArray[firstRegNum]; - u8 *vy = &cpu.registerArray[secondRegNum]; - - u8 opFourthNibble = (opLowByte & 0x0F); - // LD Vx, Vy - 8xy0 - Set Vx = Vy - if (opFourthNibble == 0x00) - { - *vx = *vy; - } - // OR Vx, Vy - 8xy1 - Set Vx = Vx OR Vy - else if (opFourthNibble == 0x01) - { - u8 result = (*vx | *vy); - *vx = result; - } - // AND Vx, Vy - 8xy2 - Set Vx = Vx AND Vy - else if (opFourthNibble == 0x02) - { - u8 result = (*vx & *vy); - *vx = result; - } - // XOR Vx, Vy - 8xy3 - Set Vx = Vx XOR Vy - else if (opFourthNibble == 0x03) - { - u8 result = (*vx ^ *vy); - *vx = result; - } - // ADD Vx, Vy - 8xy4 - Set Vx = Vx + Vy, set VF = carry - else if (opFourthNibble == 0x04) - { - u16 result = (*vx + *vy); - *vx = (result > 255) ? (u8)(result - 256) : (u8)result; - - cpu.VF = (result > 255) ? 1 : 0; - } - // SUB Vx, Vy - 8xy5 - Set Vx = Vx - Vy, set VF = NOT borrow - else if (opFourthNibble == 0x05) - { - if (*vx > *vy) + // CLS - 00E0 - Clear the display + if (opLowByte == 0xE0) { - cpu.VF = 1; - *vx -= *vy; + dchip8_init_display(renderBuffer); } + // RET - 00EE - Return from subroutine + else if (opLowByte == 0xEE) + { + cpu.programCounter = cpu.stack[--cpu.stackPointer]; + } + } + break; + + case 0x10: + case 0x20: + { + u16 loc = ((0x0F & opHighByte) << 8) | opLowByte; + DQNT_ASSERT(loc <= 0x0FFF); + + // JP addr - 1nnn - Jump to location nnn + if (opFirstNibble == 0x10) + { + // NOTE: Jump to loc, as per below + } + // Call addr - 2nnn - Call subroutine at nnn else { - cpu.VF = 0; - *vx = (u8)(256 + *vx - *vy); + DQNT_ASSERT(opFirstNibble == 0x20); + cpu.stack[cpu.stackPointer++] = cpu.programCounter; + DQNT_ASSERT(cpu.stackPointer < + DQNT_ARRAY_COUNT(cpu.stack)); } + cpu.programCounter = loc; } - // SHR Vx {, Vy} - 8xy6 - Set Vx = Vx SHR 1 - else if (opFourthNibble == 0x06) - { - if (*vx & 1) - cpu.VF = 1; - else - cpu.VF = 0; + break; - *vx >>= 1; - } - // SUBN Vx {, Vy} - 8xy7 - Set Vx = Vy - Vx, set VF = NOT borrow - else if (opFourthNibble == 0x07) + case 0x30: + case 0x40: { - if (*vy > *vx) + u8 regNum = (0x0F & opHighByte); + DQNT_ASSERT(regNum < DQNT_ARRAY_COUNT(cpu.registerArray)); + u8 *vx = &cpu.registerArray[regNum]; + + u8 valToCheck = opLowByte; + + // SE Vx, byte - 3xkk - Skip next instruction if Vx == kk + if (opFirstNibble == 0x30) { - cpu.VF = 1; - *vx = *vy - *vx; + if (*vx == valToCheck) cpu.programCounter += 2; } + // SNE Vx, byte - 4xkk - Skip next instruction if Vx == kk else { - cpu.VF = 0; - *vx = (u8)(256 + *vy - *vx); + DQNT_ASSERT(opFirstNibble == 0x40); + if (*vx != valToCheck) cpu.programCounter += 2; } - } - // SHL Vx {, Vy} - 8xyE - Set Vx = SHL 1 - else - { - DQNT_ASSERT(opFourthNibble == 0x0E); - if ((*vx >> 7) == 1) - cpu.VF = 1; - else - cpu.VF = 0; + break; - *vx <<= 1; + // SE Vx, Vy - 5xy0 - Skip next instruction if Vx = Vy + case 0x50: + { + u8 firstRegNum = (0x0F & opHighByte); + DQNT_ASSERT(firstRegNum < + DQNT_ARRAY_COUNT(cpu.registerArray)); + + u8 secondRegNum = (0xF0 & opLowByte) >> 4; + DQNT_ASSERT(secondRegNum < + DQNT_ARRAY_COUNT(cpu.registerArray)); + + u8 *vx = &cpu.registerArray[firstRegNum]; + u8 *vy = &cpu.registerArray[secondRegNum]; + + if (*vx == *vy) cpu.programCounter += 2; } - } - break; + break; - // SNE Vx, Vy - 9xy0 - Skip next instruction if Vx != Vy - case 0x90: - { - u8 firstRegNum = (0x0F & opHighByte); - DQNT_ASSERT(firstRegNum < DQNT_ARRAY_COUNT(cpu.registerArray)); - - u8 secondRegNum = (0xF0 & opLowByte) >> 4; - DQNT_ASSERT(secondRegNum < DQNT_ARRAY_COUNT(cpu.registerArray)); - - u8 *vx = &cpu.registerArray[firstRegNum]; - u8 *vy = &cpu.registerArray[secondRegNum]; - - if (*vx != *vy) cpu.programCounter += 2; - } - break; - - // LD I, addr - Annn - Set I = nnn - case 0xA0: - { - u16 valToSet = ((0x0F & opHighByte) << 8) | opLowByte; - cpu.indexRegister = valToSet; - } - break; - - // JP V0, addr - Bnnn - Jump to location (nnn + V0) - case 0xB0: - { - u16 addr = (((0x0F & opHighByte) << 8) | opLowByte) + cpu.V0; - cpu.programCounter = addr; - } - break; - - // RND Vx, byte - Cxkk - Set Vx = random byte AND kk - case 0xC0: - { - u8 regNum = (0x0F & opHighByte); - DQNT_ASSERT(regNum < DQNT_ARRAY_COUNT(cpu.registerArray)); - u8 *vx = &cpu.registerArray[regNum]; - - u8 randNum = (u8)dqnt_rnd_pcg_range(&pcgState, 0, 255); - u8 andBits = opLowByte; - DQNT_ASSERT(randNum >= 0 && randNum <= 255); - - *vx = (randNum & andBits); - } - break; - - // DRW Vx, Vy, nibble - Dxyn - Display n-byte sprite starting at mem - // location I at (Vx, Vy), set VF = collision - case 0xD0: - { - u8 xRegister = (0x0F & opHighByte); - u8 yRegister = (0xF0 & opLowByte) >> 4; - DQNT_ASSERT(xRegister < DQNT_ARRAY_COUNT(cpu.registerArray) && - yRegister < DQNT_ARRAY_COUNT(cpu.registerArray)); - - u8 initPosX = cpu.registerArray[xRegister]; - u8 initPosY = cpu.registerArray[yRegister]; - - u8 readNumBytesFromMem = (0x0F & opLowByte); - // NOTE: can't be more than 16 in Y according to specs. - DQNT_ASSERT(readNumBytesFromMem < 16); - - u8 *renderBitmap = (u8 *)renderBuffer.memory; - const i32 BYTES_PER_PIXEL = renderBuffer.bytesPerPixel; - i32 pitch = renderBuffer.width * BYTES_PER_PIXEL; - - bool collisionFlag = false; - for (i32 i = 0; i < readNumBytesFromMem; i++) + case 0x60: + case 0x70: { - u8 spriteBytes = mainMem[cpu.indexRegister + i]; - u8 posY = initPosY + (u8)i; - if (posY >= renderBuffer.height) posY = 0; + u8 regNum = (0x0F & opHighByte); + DQNT_ASSERT(regNum < DQNT_ARRAY_COUNT(cpu.registerArray)); + u8 valToOperateOn = opLowByte; - // NOTE: Flip the Y - posY = ((u8)(renderBuffer.height-1) - posY); - - const i32 ALPHA_BYTE_INTERVAL = renderBuffer.bytesPerPixel; - const i32 BITS_IN_BYTE = 8; - i32 baseBitShift = BITS_IN_BYTE - 1; - for (i32 shift = 0; shift < BITS_IN_BYTE; shift++) + u8 *vx = &cpu.registerArray[regNum]; + // LD Vx, byte - 6xkk - Set Vx = kk + if (opFirstNibble == 0x60) { - u8 posX = initPosX + (u8)shift; + *vx = valToOperateOn; + } + // ADD Vx, byte - 7xkk - Set Vx = Vx + kk + else + { + DQNT_ASSERT(opFirstNibble == 0x70); + *vx += valToOperateOn; + } + } + break; - if (posX >= renderBuffer.width) posX = 0; - u32 bitmapOffset = - (posX * BYTES_PER_PIXEL) + (posY * pitch); + case 0x80: + { + u8 firstRegNum = (0x0F & opHighByte); + DQNT_ASSERT(firstRegNum < + DQNT_ARRAY_COUNT(cpu.registerArray)); - // NOTE: Since we are using a 4bpp bitmap, let's use the - // alpha channel to determine if a pixel is on or not. - u32 *pixel = (u32 *)(&renderBitmap[bitmapOffset]); - u8 alphaBit = (*pixel >> 24); + u8 secondRegNum = (0xF0 & opLowByte) >> 4; + DQNT_ASSERT(secondRegNum < + DQNT_ARRAY_COUNT(cpu.registerArray)); - DQNT_ASSERT(alphaBit == 0 || alphaBit == 255); - bool pixelWasOn = (alphaBit == 255) ? true : false; + u8 *vx = &cpu.registerArray[firstRegNum]; + u8 *vy = &cpu.registerArray[secondRegNum]; - i32 bitShift = baseBitShift - shift; - bool spriteBit = ((spriteBytes >> bitShift) & 1); - bool pixelIsOn = (pixelWasOn ^ spriteBit); + u8 opFourthNibble = (opLowByte & 0x0F); + // LD Vx, Vy - 8xy0 - Set Vx = Vy + if (opFourthNibble == 0x00) + { + *vx = *vy; + } + // OR Vx, Vy - 8xy1 - Set Vx = Vx OR Vy + else if (opFourthNibble == 0x01) + { + u8 result = (*vx | *vy); + *vx = result; + } + // AND Vx, Vy - 8xy2 - Set Vx = Vx AND Vy + else if (opFourthNibble == 0x02) + { + u8 result = (*vx & *vy); + *vx = result; + } + // XOR Vx, Vy - 8xy3 - Set Vx = Vx XOR Vy + else if (opFourthNibble == 0x03) + { + u8 result = (*vx ^ *vy); + *vx = result; + } + // ADD Vx, Vy - 8xy4 - Set Vx = Vx + Vy, set VF = carry + else if (opFourthNibble == 0x04) + { + u16 result = (*vx + *vy); + *vx = (result > 255) ? (u8)(result - 256) : (u8)result; - // NOTE: If caused a pixel to XOR into off, then this is - // known as a "collision" in chip8 - if (pixelWasOn && !pixelIsOn) collisionFlag = true; - - if (pixelIsOn) + cpu.VF = (result > 255) ? 1 : 0; + } + // SUB Vx, Vy - 8xy5 - Set Vx = Vx - Vy, set VF = NOT borrow + else if (opFourthNibble == 0x05) + { + if (*vx > *vy) { - *pixel = 0xFFFFFFFF; + cpu.VF = 1; + *vx -= *vy; } else { - *pixel = 0; + cpu.VF = 0; + *vx = (u8)(256 + *vx - *vy); + } + } + // SHR Vx {, Vy} - 8xy6 - Set Vx = Vx SHR 1 + else if (opFourthNibble == 0x06) + { + if (*vx & 1) + cpu.VF = 1; + else + cpu.VF = 0; + + *vx >>= 1; + } + // SUBN Vx {, Vy} - 8xy7 - Set Vx = Vy - Vx, set VF = NOT + // borrow + else if (opFourthNibble == 0x07) + { + if (*vy > *vx) + { + cpu.VF = 1; + *vx = *vy - *vx; + } + else + { + cpu.VF = 0; + *vx = (u8)(256 + *vy - *vx); + } + } + // SHL Vx {, Vy} - 8xyE - Set Vx = SHL 1 + else + { + DQNT_ASSERT(opFourthNibble == 0x0E); + if ((*vx >> 7) == 1) + cpu.VF = 1; + else + cpu.VF = 0; + + *vx <<= 1; + } + } + break; + + // SNE Vx, Vy - 9xy0 - Skip next instruction if Vx != Vy + case 0x90: + { + u8 firstRegNum = (0x0F & opHighByte); + DQNT_ASSERT(firstRegNum < + DQNT_ARRAY_COUNT(cpu.registerArray)); + + u8 secondRegNum = (0xF0 & opLowByte) >> 4; + DQNT_ASSERT(secondRegNum < + DQNT_ARRAY_COUNT(cpu.registerArray)); + + u8 *vx = &cpu.registerArray[firstRegNum]; + u8 *vy = &cpu.registerArray[secondRegNum]; + + if (*vx != *vy) cpu.programCounter += 2; + } + break; + + // LD I, addr - Annn - Set I = nnn + case 0xA0: + { + u16 valToSet = ((0x0F & opHighByte) << 8) | opLowByte; + cpu.indexRegister = valToSet; + } + break; + + // JP V0, addr - Bnnn - Jump to location (nnn + V0) + case 0xB0: + { + u16 addr = + (((0x0F & opHighByte) << 8) | opLowByte) + cpu.V0; + cpu.programCounter = addr; + } + break; + + // RND Vx, byte - Cxkk - Set Vx = random byte AND kk + case 0xC0: + { + u8 regNum = (0x0F & opHighByte); + DQNT_ASSERT(regNum < DQNT_ARRAY_COUNT(cpu.registerArray)); + u8 *vx = &cpu.registerArray[regNum]; + + u8 randNum = (u8)dqnt_rnd_pcg_range(&pcgState, 0, 255); + u8 andBits = opLowByte; + DQNT_ASSERT(randNum >= 0 && randNum <= 255); + + *vx = (randNum & andBits); + } + break; + + // DRW Vx, Vy, nibble - Dxyn - Display n-byte sprite starting at + // mem + // location I at (Vx, Vy), set VF = collision + case 0xD0: + { + u8 xRegister = (0x0F & opHighByte); + u8 yRegister = (0xF0 & opLowByte) >> 4; + DQNT_ASSERT( + xRegister < DQNT_ARRAY_COUNT(cpu.registerArray) && + yRegister < DQNT_ARRAY_COUNT(cpu.registerArray)); + + u8 initPosX = cpu.registerArray[xRegister]; + u8 initPosY = cpu.registerArray[yRegister]; + + u8 readNumBytesFromMem = (0x0F & opLowByte); + // NOTE: can't be more than 16 in Y according to specs. + DQNT_ASSERT(readNumBytesFromMem < 16); + + u8 *renderBitmap = (u8 *)renderBuffer.memory; + const i32 BYTES_PER_PIXEL = renderBuffer.bytesPerPixel; + i32 pitch = renderBuffer.width * BYTES_PER_PIXEL; + + bool collisionFlag = false; + for (i32 i = 0; i < readNumBytesFromMem; i++) + { + u8 spriteBytes = mainMem[cpu.indexRegister + i]; + u8 posY = initPosY + (u8)i; + if (posY >= renderBuffer.height) posY = 0; + + // NOTE: Flip the Y + posY = ((u8)(renderBuffer.height - 1) - posY); + + const i32 ALPHA_BYTE_INTERVAL = + renderBuffer.bytesPerPixel; + const i32 BITS_IN_BYTE = 8; + i32 baseBitShift = BITS_IN_BYTE - 1; + for (i32 shift = 0; shift < BITS_IN_BYTE; shift++) + { + u8 posX = initPosX + (u8)shift; + + if (posX >= renderBuffer.width) posX = 0; + u32 bitmapOffset = + (posX * BYTES_PER_PIXEL) + (posY * pitch); + + // NOTE: Since we are using a 4bpp bitmap, let's use + // the + // alpha channel to determine if a pixel is on or + // not. + u32 *pixel = (u32 *)(&renderBitmap[bitmapOffset]); + u8 alphaBit = (*pixel >> 24); + + DQNT_ASSERT(alphaBit == 0 || alphaBit == 255); + bool pixelWasOn = (alphaBit == 255) ? true : false; + + i32 bitShift = baseBitShift - shift; + bool spriteBit = ((spriteBytes >> bitShift) & 1); + bool pixelIsOn = (pixelWasOn ^ spriteBit); + + // NOTE: If caused a pixel to XOR into off, then + // this is + // known as a "collision" in chip8 + if (pixelWasOn && !pixelIsOn) collisionFlag = true; + + if (pixelIsOn) + { + *pixel = 0xFFFFFFFF; + } + else + { + *pixel = 0; + } + } + } + + cpu.VF = collisionFlag; + } + break; + + case 0xE0: + { + u8 regNum = (0x0F & opHighByte); + DQNT_ASSERT(regNum < DQNT_ARRAY_COUNT(cpu.registerArray)); + u8 vx = cpu.registerArray[regNum]; + DQNT_ASSERT(vx >= 0 && vx <= 0x0F); + DQNT_ASSERT(vx < DQNT_ARRAY_COUNT(controller.key)); + + bool skipNextInstruction = false; + // SKP Vx - Ex9E - Skip next instruction if key with the + // value + // of Vx is pressed + if (opLowByte == 0x9E) + { + skipNextInstruction = controller.key[vx]; + } + // SKNP Vx - ExA1 - Skip next instruction if key with the + // value + // of Vx is not pressed + else + { + DQNT_ASSERT(opLowByte == 0xA1); + skipNextInstruction = !controller.key[vx]; + } + + if (skipNextInstruction) cpu.programCounter += 2; + } + break; + + case 0xF0: + { + u8 regNum = (0x0F & opHighByte); + DQNT_ASSERT(regNum < DQNT_ARRAY_COUNT(cpu.registerArray)); + u8 *vx = &cpu.registerArray[regNum]; + + // LD Vx, DT - Fx07 - Set Vx = delay timer value + if (opLowByte == 0x07) + { + *vx = cpu.delayTimer; + } + // LD Vx, K - Fx0A - Wait for a key press, store the value + // of + // the key in Vx + else if (opLowByte == 0x0A) + { + cpu.state = chip8state_await_input; + cpu.storeKeyToRegisterIndex = regNum; + earlyExit = true; + } + // LD DT, Vx - Fx15 - Set delay timer = Vx + else if (opLowByte == 0x15) + { + cpu.delayTimer = *vx; + } + // LD ST, Vx - Fx18 - Set sound timer = Vx + else if (opLowByte == 0x18) + { + cpu.soundTimer = *vx; + } + // ADD I, Vx - Fx1E - Set I = I + Vx + else if (opLowByte == 0x1E) + { + cpu.indexRegister += *vx; + } + // LD F, Vx - Fx29 - Set I = location of sprite for digit Vx + else if (opLowByte == 0x29) + { + u8 hexCharFromFontSet = *vx; + DQNT_ASSERT(hexCharFromFontSet >= 0x00 && + hexCharFromFontSet <= 0x0F); + + const u32 START_ADDR_OF_FONT = 0; + const u32 BYTES_PER_FONT = 5; + + cpu.I = START_ADDR_OF_FONT + + (hexCharFromFontSet * BYTES_PER_FONT); + } + // LD B, Vx - Fx33 - Store BCD representations of Vx in + // memory + // locations I, I+1 and I+2 + else if (opLowByte == 0x33) + { + DQNT_ASSERT(regNum < + DQNT_ARRAY_COUNT(cpu.registerArray)); + u8 vxVal = *vx; + + const i32 NUM_DIGITS_IN_HUNDREDS = 3; + for (i32 i = 0; i < NUM_DIGITS_IN_HUNDREDS; i++) + { + u8 rem = vxVal % 10; + vxVal /= 10; + + mainMem[cpu.I + + ((NUM_DIGITS_IN_HUNDREDS - 1) - i)] = rem; + } + } + // LD [I], Vx - Fx55 - Store register V0 through Vx in + // memory + // starting at location I. + else if (opLowByte == 0x55) + { + for (u32 regIndex = 0; regIndex <= regNum; regIndex++) + { + u32 mem_offset = regIndex; + mainMem[cpu.indexRegister + mem_offset] = + cpu.registerArray[regIndex]; + } + } + // LD [I], Vx - Fx65 - Read registers V0 through Vx from + // memory + // starting at location I. + else + { + DQNT_ASSERT(opLowByte == 0x65); + for (u32 regIndex = 0; regIndex <= regNum; regIndex++) + { + u32 mem_offset = regIndex; + cpu.registerArray[regIndex] = + mainMem[cpu.indexRegister + mem_offset]; } } } - - cpu.VF = collisionFlag; - } - break; - - case 0xE0: - { - u8 regNum = (0x0F & opHighByte); - DQNT_ASSERT(regNum < DQNT_ARRAY_COUNT(cpu.registerArray)); - u8 vx = cpu.registerArray[regNum]; - DQNT_ASSERT(vx >= 0 && vx <= 0x0F); - DQNT_ASSERT(vx < DQNT_ARRAY_COUNT(controller.key)); - - bool skipNextInstruction = false; - // SKP Vx - Ex9E - Skip next instruction if key with the value - // of Vx is pressed - if (opLowByte == 0x9E) - { - skipNextInstruction = controller.key[vx]; - } - // SKNP Vx - ExA1 - Skip next instruction if key with the value - // of Vx is not pressed - else - { - DQNT_ASSERT(opLowByte == 0xA1); - skipNextInstruction = !controller.key[vx]; - } - - if (skipNextInstruction) cpu.programCounter += 2; - } - break; - - case 0xF0: - { - u8 regNum = (0x0F & opHighByte); - DQNT_ASSERT(regNum < DQNT_ARRAY_COUNT(cpu.registerArray)); - u8 *vx = &cpu.registerArray[regNum]; - - // LD Vx, DT - Fx07 - Set Vx = delay timer value - if (opLowByte == 0x07) - { - *vx = cpu.delayTimer; - } - // LD Vx, K - Fx0A - Wait for a key press, store the value of - // the key in Vx - else if (opLowByte == 0x0A) - { - cpu.state = chip8state_await_input; - cpu.storeKeyToRegisterIndex = regNum; - } - // LD DT, Vx - Fx15 - Set delay timer = Vx - else if (opLowByte == 0x15) - { - cpu.delayTimer = *vx; - } - // LD ST, Vx - Fx18 - Set sound timer = Vx - else if (opLowByte == 0x18) - { - cpu.soundTimer = *vx; - } - // ADD I, Vx - Fx1E - Set I = I + Vx - else if (opLowByte == 0x1E) - { - cpu.indexRegister += *vx; - } - // LD F, Vx - Fx29 - Set I = location of sprite for digit Vx - else if (opLowByte == 0x29) - { - u8 hexCharFromFontSet = *vx; - DQNT_ASSERT(hexCharFromFontSet >= 0x00 && - hexCharFromFontSet <= 0x0F); - - const u32 START_ADDR_OF_FONT = 0; - const u32 BYTES_PER_FONT = 5; - - cpu.I = START_ADDR_OF_FONT + - (hexCharFromFontSet * BYTES_PER_FONT); - } - // LD B, Vx - Fx33 - Store BCD representations of Vx in memory - // locations I, I+1 and I+2 - else if (opLowByte == 0x33) - { - DQNT_ASSERT(regNum < DQNT_ARRAY_COUNT(cpu.registerArray)); - u8 vxVal = *vx; - - const i32 NUM_DIGITS_IN_HUNDREDS = 3; - for (i32 i = 0; i < NUM_DIGITS_IN_HUNDREDS; i++) - { - u8 rem = vxVal % 10; - vxVal /= 10; - - mainMem[cpu.I + ((NUM_DIGITS_IN_HUNDREDS-1) - i)] = rem; - } - } - // LD [I], Vx - Fx55 - Store register V0 through Vx in memory - // starting at location I. - else if (opLowByte == 0x55) - { - for (u32 regIndex = 0; regIndex <= regNum; regIndex++) - { - u32 mem_offset = regIndex; - mainMem[cpu.indexRegister + mem_offset] = - cpu.registerArray[regIndex]; - } - } - // LD [I], Vx - Fx65 - Read registers V0 through Vx from memory - // starting at location I. - else - { - DQNT_ASSERT(opLowByte == 0x65); - for (u32 regIndex = 0; regIndex <= regNum; regIndex++) - { - u32 mem_offset = regIndex; - cpu.registerArray[regIndex] = - mainMem[cpu.indexRegister + mem_offset]; - } - } - } - break; - }; - + break; + }; + } // IMPORTANT: Timers need to be decremented at a rate of 60hz. Since we // can run the interpreter faster than that, make sure we decrement // timers at the fixed rate. @@ -819,9 +844,16 @@ void dchip8_update(PlatformRenderBuffer renderBuffer, PlatformInput input, { cpu.elapsedTime = 0; if (cpu.delayTimer > 0) cpu.delayTimer--; - // TODO(doyle): This needs to play a buzzing sound whilst timer - // > 0 - if (cpu.soundTimer > 0) cpu.soundTimer--; + + if (cpu.soundTimer > 0) + { + cpu.soundTimer--; + if (cpu.soundTimer == 1) + { + // TODO(doyle): This needs to play a buzzing sound + // whilst timer > 0 + } + } } } else diff --git a/src/dchip8.h b/src/dchip8.h index 488ced9..6cd45e6 100644 --- a/src/dchip8.h +++ b/src/dchip8.h @@ -4,6 +4,6 @@ #include "dchip8_platform.h" void dchip8_update(PlatformRenderBuffer renderBuffer, PlatformInput input, - PlatformMemory memory); + PlatformMemory memory, u32 cyclesToEmulate); #endif diff --git a/src/dchip8_platform.h b/src/dchip8_platform.h index abe5ea1..1e84aba 100644 --- a/src/dchip8_platform.h +++ b/src/dchip8_platform.h @@ -46,8 +46,8 @@ enum Key typedef struct KeyState { - bool isDown; - u32 transitionCount; + bool endedDown; + u32 halfTransitionCount; } KeyState; typedef struct PlatformInput diff --git a/src/win32_dchip8.cpp b/src/win32_dchip8.cpp index e90a6d2..c40787a 100644 --- a/src/win32_dchip8.cpp +++ b/src/win32_dchip8.cpp @@ -115,14 +115,13 @@ inline FILE_SCOPE LARGE_INTEGER win32_query_perf_counter_time() return result; } -FILE_SCOPE inline void win32_parse_key_msg(KeyState *key, MSG msg) +FILE_SCOPE inline void win32_update_key(KeyState *key, bool isDown) { - LPARAM lParam = msg.lParam; - bool keyIsDown = ((lParam >> 30) & 1); - bool keyTransitioned = ((lParam >> 31) & 1); - - key->isDown = keyIsDown; - if (keyTransitioned) key->transitionCount++; + if (key->endedDown != isDown) + { + key->endedDown = isDown; + key->halfTransitionCount++; + } } FILE_SCOPE void win32_process_messages(HWND window, PlatformInput *input) @@ -137,37 +136,38 @@ FILE_SCOPE void win32_process_messages(HWND window, PlatformInput *input) case WM_KEYDOWN: case WM_KEYUP: { + bool isDown = (msg.message == WM_KEYDOWN); switch (msg.wParam) { - case VK_UP: win32_parse_key_msg(&input->up, msg); break; - case VK_DOWN: win32_parse_key_msg(&input->down, msg); break; - case VK_LEFT: win32_parse_key_msg(&input->left, msg); break; - case VK_RIGHT: win32_parse_key_msg(&input->right, msg); break; + case VK_UP: win32_update_key(&input->up, isDown); break; + case VK_DOWN: win32_update_key(&input->down, isDown); break; + case VK_LEFT: win32_update_key(&input->left, isDown); break; + case VK_RIGHT: win32_update_key(&input->right, isDown); break; - case '1': win32_parse_key_msg(&input->key_1, msg); break; - case '2': win32_parse_key_msg(&input->key_2, msg); break; - case '3': win32_parse_key_msg(&input->key_3, msg); break; - case '4': win32_parse_key_msg(&input->key_4, msg); break; + case '1': win32_update_key(&input->key_1, isDown); break; + case '2': win32_update_key(&input->key_2, isDown); break; + case '3': win32_update_key(&input->key_3, isDown); break; + case '4': win32_update_key(&input->key_4, isDown); break; - case 'Q': win32_parse_key_msg(&input->key_q, msg); break; - case 'W': win32_parse_key_msg(&input->key_w, msg); break; - case 'E': win32_parse_key_msg(&input->key_e, msg); break; - case 'R': win32_parse_key_msg(&input->key_r, msg); break; + case 'Q': win32_update_key(&input->key_q, isDown); break; + case 'W': win32_update_key(&input->key_w, isDown); break; + case 'E': win32_update_key(&input->key_e, isDown); break; + case 'R': win32_update_key(&input->key_r, isDown); break; - case 'A': win32_parse_key_msg(&input->key_a, msg); break; - case 'S': win32_parse_key_msg(&input->key_s, msg); break; - case 'D': win32_parse_key_msg(&input->key_d, msg); break; - case 'F': win32_parse_key_msg(&input->key_f, msg); break; + case 'A': win32_update_key(&input->key_a, isDown); break; + case 'S': win32_update_key(&input->key_s, isDown); break; + case 'D': win32_update_key(&input->key_d, isDown); break; + case 'F': win32_update_key(&input->key_f, isDown); break; - case 'Z': win32_parse_key_msg(&input->key_z, msg); break; - case 'X': win32_parse_key_msg(&input->key_x, msg); break; - case 'C': win32_parse_key_msg(&input->key_c, msg); break; - case 'V': win32_parse_key_msg(&input->key_v, msg); break; + case 'Z': win32_update_key(&input->key_z, isDown); break; + case 'X': win32_update_key(&input->key_x, isDown); break; + case 'C': win32_update_key(&input->key_c, isDown); break; + case 'V': win32_update_key(&input->key_v, isDown); break; case VK_ESCAPE: { - win32_parse_key_msg(&input->escape, msg); - if (input->escape.isDown) globalRunning = false; + win32_update_key(&input->escape, isDown); + if (input->escape.endedDown) globalRunning = false; } break; @@ -279,7 +279,7 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, platformMemory.permanentMemSize = DQNT_ARRAY_COUNT(stackMemory); QueryPerformanceFrequency(&globalQueryPerformanceFrequency); - const f32 TARGET_FRAMES_PER_S = 540.0f; + const f32 TARGET_FRAMES_PER_S = 30.0f; f32 targetSecondsPerFrame = 1 / TARGET_FRAMES_PER_S; f32 frameTimeInS = 0.0f; globalRunning = true; @@ -300,13 +300,7 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, platformBuffer.height = globalRenderBitmap.height; platformBuffer.width = globalRenderBitmap.width; platformBuffer.bytesPerPixel = globalRenderBitmap.bytesPerPixel; - dchip8_update(platformBuffer, platformInput, platformMemory); - - for (i32 i = 0; i < DQNT_ARRAY_COUNT(platformInput.key); i++) - { - if (platformInput.key[i].isDown) - OutputDebugString(L"Key is down\n"); - } + dchip8_update(platformBuffer, platformInput, platformMemory, 15); } ////////////////////////////////////////////////////////////////////////