From ac6d32d1b9686e90e77b7ba6aa2aab72a5833517 Mon Sep 17 00:00:00 2001 From: Doyle Thai Date: Wed, 5 Apr 2017 23:08:40 +1000 Subject: [PATCH] Implement most of the drawing routine --- src/build.bat | 3 +- src/dchip8.cpp | 177 +++++++++++++++++++++++++++++++++--------- src/dchip8_platform.h | 4 +- src/dqnt.h | 46 +++++++++-- src/win32_dchip8.cpp | 29 ++++--- 5 files changed, 201 insertions(+), 58 deletions(-) diff --git a/src/build.bat b/src/build.bat index 2d67660..7ae8cf9 100644 --- a/src/build.bat +++ b/src/build.bat @@ -35,8 +35,9 @@ REM WX treat warnings as errors REM wd4100 ignore: unused argument parameters REM wd4201 ignore: nonstandard extension used: nameless struct/union REM wd4189 ignore: local variable is initialised but not referenced +REM wd4505 ignore: unreference local functions that will be removed -set CompileFlags=-EHa- -GR- -Oi -MT -Z7 -W4 -WX -wd4100 -wd4201 -wd4189 +set CompileFlags=-EHa- -GR- -Oi -MT -Z7 -W4 -WX -wd4100 -wd4201 -wd4189 -wd4505 REM Include directories set IncludeFlags= diff --git a/src/dchip8.cpp b/src/dchip8.cpp index a8ce497..955be53 100644 --- a/src/dchip8.cpp +++ b/src/dchip8.cpp @@ -73,7 +73,6 @@ FILE_SCOPE RandPCGState pcgState; FILE_SCOPE void dchip8_init_memory(u8 *memory) { - DQNT_ASSERT(memory.permanentMemSize == 4096); const u8 PRESET_FONTS[] = { // "0" @@ -206,14 +205,10 @@ FILE_SCOPE void dchip8_init_cpu(Chip8CPU *chip8CPU) chip8CPU->state = chip8state_load_file; } -void dchip8_update(PlatformRenderBuffer renderBuffer, PlatformInput input, - PlatformMemory memory) +FILE_SCOPE +void dchip8_debug_draw_half_colored_screen_internal( + PlatformRenderBuffer renderBuffer) { - DQNT_ASSERT(indexRegister >= 0 && indexRegister <= 0xFFF); - DQNT_ASSERT(programCounter >= 0 && programCounter <= 0xFFF); - - DQNT_ASSERT(renderBuffer.bytesPerPixel == 4); - const i32 numPixels = renderBuffer.width * renderBuffer.height; u32 *bitmapBuffer = (u32 *)renderBuffer.memory; for (i32 i = 0; i < numPixels; i++) @@ -251,22 +246,49 @@ void dchip8_update(PlatformRenderBuffer renderBuffer, PlatformInput input, bitmapBuffer[i] = color; } } +} + +FILE_SCOPE void dchip8_init_display(PlatformRenderBuffer renderBuffer) +{ + // Init screen to 0 alpha, and let alpha simulate "turning on a pixel" + i32 numPixels = renderBuffer.width * renderBuffer.height; + u32 *bitmapBuffer = (u32 *)renderBuffer.memory; + for (i32 i = 0; i < numPixels; i++) + { + u8 r = (u8)(0.0f); + u8 g = (u8)(0.0f); + u8 b = (u8)(0.0f); + u8 a = (u8)(0.0f); + + u32 color = (a << 24) | (r << 16) | (g << 8) | (b << 0); + bitmapBuffer[i] = color; + } +} + +void dchip8_update(PlatformRenderBuffer renderBuffer, PlatformInput input, + PlatformMemory memory) +{ + DQNT_ASSERT(cpu.indexRegister >= 0 && cpu.indexRegister <= 0xFFF); + DQNT_ASSERT(cpu.programCounter >= 0 && cpu.programCounter <= 0xFFF); + DQNT_ASSERT(renderBuffer.bytesPerPixel == 4); + DQNT_ASSERT(memory.permanentMemSize == 4096); u8 *mainMem = (u8 *)memory.permanentMem; if (cpu.state == chip8state_init) { dchip8_init_cpu(&cpu); dchip8_init_memory(mainMem); + dchip8_init_display(renderBuffer); } -#if 0 +#if 1 if (cpu.state == chip8state_load_file) { PlatformFile file = {}; - if (platform_open_file(L"roms/PONG", &file)) + if (platform_open_file(L"roms/BLITZ", &file)) { DQNT_ASSERT((cpu.INIT_ADDRESS + file.size) <= - DQNT_ARRAY_COUNT(mainMem)); + memory.permanentMemSize); void *loadToAddr = (void *)(&mainMem[cpu.INIT_ADDRESS]); if (platform_read_file(file, loadToAddr, (u32)file.size)) @@ -296,15 +318,12 @@ void dchip8_update(PlatformRenderBuffer renderBuffer, PlatformInput input, // CLS - 00E0 - Clear the display if (opLowByte == 0xE0) { - u32 numBytesToClear = renderBuffer.width * - renderBuffer.height * - renderBuffer.bytesPerPixel; - memset(renderBuffer.memory, 0, numBytesToClear); + dchip8_init_display(renderBuffer); } // RET - 00EE - Return from subroutine else if (opLowByte == 0xEE) { - cpu.programCounter = cpu.stack[cpu.stackPointer--]; + cpu.programCounter = cpu.stack[--cpu.stackPointer]; } } break; @@ -312,7 +331,7 @@ void dchip8_update(PlatformRenderBuffer renderBuffer, PlatformInput input, case 0x10: case 0x20: { - u16 loc = ((0x0F & opHighByte) << 8) | (0xFF & opLowByte); + u16 loc = ((0x0F & opHighByte) << 8) | opLowByte; DQNT_ASSERT(loc <= 0x0FFF); // JP addr - 1nnn - Jump to location nnn @@ -324,10 +343,8 @@ void dchip8_update(PlatformRenderBuffer renderBuffer, PlatformInput input, else { DQNT_ASSERT(opFirstNibble == 0x20); - - cpu.stackPointer++; + cpu.stack[cpu.stackPointer++] = cpu.programCounter; DQNT_ASSERT(cpu.stackPointer < DQNT_ARRAY_COUNT(cpu.stack)); - cpu.stack[cpu.stackPointer] = cpu.programCounter; } cpu.programCounter = loc; @@ -363,7 +380,7 @@ void dchip8_update(PlatformRenderBuffer renderBuffer, PlatformInput input, u8 firstRegNum = (0x0F & opHighByte); DQNT_ASSERT(firstRegNum < DQNT_ARRAY_COUNT(cpu.registerArray)); - u8 secondRegNum = (0xF0 & opLowByte); + u8 secondRegNum = (0xF0 & opLowByte) >> 4; DQNT_ASSERT(secondRegNum < DQNT_ARRAY_COUNT(cpu.registerArray)); if (cpu.registerArray[firstRegNum] == @@ -400,37 +417,38 @@ void dchip8_update(PlatformRenderBuffer renderBuffer, PlatformInput input, u8 firstRegNum = (0x0F & opHighByte); DQNT_ASSERT(firstRegNum < DQNT_ARRAY_COUNT(cpu.registerArray)); - u8 secondRegNum = (0xF0 & opLowByte); + 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 (opLowByte == 0x00) + if (opFourthNibble == 0x00) { *vx = *vy; } // OR Vx, Vy - 8xy1 - Set Vx = Vx OR Vy - else if (opLowByte == 0x01) + else if (opFourthNibble == 0x01) { u8 result = (*vx | *vy); *vx = result; } // AND Vx, Vy - 8xy2 - Set Vx = Vx AND Vy - else if (opLowByte == 0x02) + else if (opFourthNibble == 0x02) { u8 result = (*vx & *vy); *vx = result; } // XOR Vx, Vy - 8xy3 - Set Vx = Vx XOR Vy - else if (opLowByte == 0x03) + else if (opFourthNibble == 0x03) { u8 result = (*vx & *vy); *vx = result; } // ADD Vx, Vy - 8xy4 - Set Vx = Vx + Vy, set VF = carry - else if (opLowByte == 0x04) + else if (opFourthNibble == 0x04) { u16 result = (*vx + *vy); *vx = (u8)result; @@ -438,7 +456,7 @@ void dchip8_update(PlatformRenderBuffer renderBuffer, PlatformInput input, if (result > 255) cpu.VF = (result > 255) ? 1 : 0; } // SUB Vx, Vy - 8xy5 - Set Vx = Vx - Vy, set VF = NOT borrow - else if (opLowByte == 0x05) + else if (opFourthNibble == 0x05) { if (*vx > *vy) cpu.VF = 1; @@ -448,7 +466,7 @@ void dchip8_update(PlatformRenderBuffer renderBuffer, PlatformInput input, *vx -= *vy; } // SHR Vx {, Vy} - 8xy6 - Set Vx = Vx SHR 1 - else if (opLowByte == 0x06) + else if (opFourthNibble == 0x06) { if (*vx & 1) cpu.VF = 1; @@ -458,7 +476,7 @@ void dchip8_update(PlatformRenderBuffer renderBuffer, PlatformInput input, *vx >>= 1; } // SUBN Vx {, Vy} - 8xy7 - Set Vx = Vy - Vx, set VF = NOT borrow - else if (opLowByte == 0x07) + else if (opFourthNibble == 0x07) { if (*vy > *vx) cpu.VF = 1; @@ -470,7 +488,7 @@ void dchip8_update(PlatformRenderBuffer renderBuffer, PlatformInput input, // SHL Vx {, Vy} - 8xyE - Set Vx = SHL 1 else { - DQNT_ASSERT(opLowByte == 0x0E); + DQNT_ASSERT(opFourthNibble == 0x0E); if ((*vx >> 7) == 1) cpu.VF = 1; else @@ -487,7 +505,7 @@ void dchip8_update(PlatformRenderBuffer renderBuffer, PlatformInput input, u8 firstRegNum = (0x0F & opHighByte); DQNT_ASSERT(firstRegNum < DQNT_ARRAY_COUNT(cpu.registerArray)); - u8 secondRegNum = (0xF0 & opLowByte); + u8 secondRegNum = (0xF0 & opLowByte) >> 4; DQNT_ASSERT(secondRegNum < DQNT_ARRAY_COUNT(cpu.registerArray)); u8 *vx = &cpu.registerArray[firstRegNum]; @@ -500,7 +518,7 @@ void dchip8_update(PlatformRenderBuffer renderBuffer, PlatformInput input, // LD I, addr - Annn - Set I = nnn case 0xA0: { - u8 valToSet = opLowByte; + u16 valToSet = ((0x0F & opHighByte) << 8) | opLowByte; cpu.indexRegister = valToSet; } break; @@ -531,14 +549,73 @@ void dchip8_update(PlatformRenderBuffer renderBuffer, PlatformInput input, // location I at (Vx, Vy), set VF = collision case 0xD0: { - // TODO(doyle): Implement drawing - u8 posX = (0x0F & opHighByte); - u8 posY = (0xF0 & opLowByte); + 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; for (i32 i = 0; i < readNumBytesFromMem; i++) { - u8 *memPtr = &mainMem[cpu.indexRegister + i]; + u8 spriteBytes = mainMem[cpu.indexRegister + i]; + u8 posY = initPosY + (u8)i; + + if (posY >= renderBuffer.height) posY = 0; + + 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); + + // TODO(doyle): wrap pixels around + // NOTE: If caused a pixel to XOR into off, then this is + // known as a "collision" in chip8 + if (pixelWasOn && !pixelIsOn) + { + cpu.VF = 1; + } + else + { + cpu.VF = 0; + } + + if (pixelIsOn) + { + *pixel = 0xFFFFFFFF; + } + else + { + *pixel = 0; + } + } } } break; @@ -599,12 +676,31 @@ void dchip8_update(PlatformRenderBuffer renderBuffer, PlatformInput input, // LD F, Vx - Fx29 - Set I = location of sprite for digit Vx else if (opLowByte == 0x29) { - // TODO: Implement + u8 hexCharFromFontSet = (0x0F & opHighByte); + DQNT_ASSERT(hexCharFromFontSet >= 0x00 && + hexCharFromFontSet <= 0x0F); + + const u8 BITS_IN_BYTE = 8; + u8 fontSizeInMem = BITS_IN_BYTE * 5; + u16 startMemAddrOfFontSet = 0; + cpu.I = (hexCharFromFontSet * fontSizeInMem) * + startMemAddrOfFontSet; } // 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. @@ -632,6 +728,11 @@ void dchip8_update(PlatformRenderBuffer renderBuffer, PlatformInput input, } break; }; + + if (cpu.delayTimer > 0) cpu.delayTimer--; + + // TODO(doyle): This needs to play a buzzing sound whilst timer > 0 + if (cpu.soundTimer > 0) cpu.soundTimer--; } } diff --git a/src/dchip8_platform.h b/src/dchip8_platform.h index cd392b4..2d47c6d 100644 --- a/src/dchip8_platform.h +++ b/src/dchip8_platform.h @@ -3,8 +3,8 @@ #include "dqnt.h" -// NOTE: Platform buffers are expected to be bottom to top! I.e. origin is -// bottom left. +// NOTE: Platform buffers are expected to be top to bottom! I.e. origin is top +// left, also 4 bytes per pixel with packing order A R G B typedef struct PlatformRenderBuffer { i32 width; diff --git a/src/dqnt.h b/src/dqnt.h index 8414e99..31f8acd 100644 --- a/src/dqnt.h +++ b/src/dqnt.h @@ -26,13 +26,7 @@ typedef float f32; #define DQNT_INVALID_CODE_PATH 0 #define DQNT_ARRAY_COUNT(array) (sizeof(array) / sizeof(array[0])) - -#ifdef DEBUG_MODE - #define DQNT_ASSERT(expr) if (!(expr)) { (*((i32 *)0)) = 0; } -#else - #define DQNT_ASSERT(expr) -#endif - +#define DQNT_ASSERT(expr) if (!(expr)) { (*((i32 *)0)) = 0; } #define DQNT_MATH_ABS(x) (((x) < 0) ? (-(x)) : (x)) //////////////////////////////////////////////////////////////////////////////// @@ -61,6 +55,7 @@ char *dqnt_strncpy(char *dest, const char *src, i32 numChars); bool dqnt_str_reverse(char *const buf, const i32 bufSize); i32 dqnt_str_to_i32 (char *const buf, const i32 bufSize); +void dqnt_i32_to_str (i32 value, char *buf, i32 bufSize); wchar_t dqnt_wchar_ascii_to_lower(wchar_t character); i32 dqnt_wstrcmp(const wchar_t *a, const wchar_t *b); @@ -208,6 +203,43 @@ i32 dqnt_str_to_i32(char *const buf, const i32 bufSize) return result; } +void dqnt_i32_to_str(i32 value, char *buf, i32 bufSize) +{ + if (!buf || bufSize == 0) return; + + if (value == 0) + { + buf[0] = '0'; + return; + } + + // NOTE(doyle): Max 32bit integer (+-)2147483647 + i32 charIndex = 0; + bool negative = false; + if (value < 0) negative = true; + + if (negative) buf[charIndex++] = '-'; + + i32 val = DQNT_MATH_ABS(value); + while (val != 0 && charIndex < bufSize) + { + i32 rem = val % 10; + buf[charIndex++] = (u8)rem + '0'; + val /= 10; + } + + // NOTE(doyle): If string is negative, we only want to reverse starting + // from the second character, so we don't put the negative sign at the end + if (negative) + { + dqnt_str_reverse(buf + 1, charIndex - 1); + } + else + { + dqnt_str_reverse(buf, charIndex); + } +} + wchar_t dqnt_wchar_ascii_to_lower(wchar_t character) { if (character >= L'A' && character <= L'Z') diff --git a/src/win32_dchip8.cpp b/src/win32_dchip8.cpp index 61b486e..e47da40 100644 --- a/src/win32_dchip8.cpp +++ b/src/win32_dchip8.cpp @@ -71,20 +71,28 @@ FILE_SCOPE void win32_display_render_bitmap(Win32RenderBitmap renderBitmap, HDC deviceContext, LONG width, LONG height) { + HDC stretchDC = CreateCompatibleDC(deviceContext); + SelectObject(stretchDC, renderBitmap.handle); + + StretchBlt(deviceContext, 0, 0, width, height, stretchDC, 0, + 0, renderBitmap.width, renderBitmap.height, SRCCOPY); + + // NOTE: Win32 AlphaBlend requires the RGB components to be premultiplied + // with alpha. +#if 0 BLENDFUNCTION blend = {}; blend.BlendOp = AC_SRC_OVER; blend.SourceConstantAlpha = 255; blend.AlphaFormat = AC_SRC_ALPHA; - HDC alphaBlendDC = CreateCompatibleDC(deviceContext); - SelectObject(alphaBlendDC, renderBitmap.handle); + if (!AlphaBlend(deviceContext, 0, 0, width, height, deviceContext, 0, 0, + width, height, blend)) + { + OutputDebugString(L"AlphaBlend() failed."); + } +#endif - AlphaBlend(deviceContext, 0, 0, width, height, alphaBlendDC, 0, 0, - renderBitmap.width, renderBitmap.height, blend); - StretchBlt(deviceContext, 0, 0, width, height, deviceContext, 0, 0, - renderBitmap.width, renderBitmap.height, SRCCOPY); - - DeleteDC(alphaBlendDC); + DeleteDC(stretchDC); } FILE_SCOPE inline void win32_parse_key_msg(KeyState *key, MSG msg) @@ -172,8 +180,8 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, // is slightly smaller than 800x600. Windows provides a function to help // calculate the size you'd need by accounting for the window style. RECT rect = {}; - rect.right = 128; - rect.bottom = 64; + rect.right = 256; + rect.bottom = 128; DWORD windowStyle = WS_OVERLAPPEDWINDOW | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN; @@ -253,6 +261,7 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, platformBuffer.memory = renderBitmap.memory; platformBuffer.height = renderBitmap.height; platformBuffer.width = renderBitmap.width; + platformBuffer.bytesPerPixel = renderBitmap.bytesPerPixel; dchip8_update(platformBuffer, platformInput, platformMemory); }