Compare commits
7 Commits
9ffb5088f5
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| ee9f65f016 | |||
| d522c16206 | |||
| 7b9b6f3200 | |||
| 2a1b24af1f | |||
| 5ddc837101 | |||
| f70ac0595c | |||
| 8f93c13807 |
@@ -1,2 +0,0 @@
|
||||
call build || exit /b %errorlevel%
|
||||
devenv .\build\handmade_win32.exe
|
||||
@@ -1,2 +0,0 @@
|
||||
call build || exit /b %errorlevel%
|
||||
.\build\handmade_win32.exe
|
||||
@@ -9,7 +9,7 @@ set commonCompilerFlags=^
|
||||
-Oi %= Always use intrinsics =%^
|
||||
-EHa- %= Disable exception handling =%^
|
||||
-GR- %= Never use runtime type info from C++ =%^
|
||||
-WX -W4 -wd4201 -wd4100 -wd4189 %= Compiler warnings, -WX warnings as errors, -W4 warning level 4, -wdXXXX disable warning XXXX =%^
|
||||
-WX -W4 -wd4201 -wd4100 -wd4189 -wd4505 %= Compiler warnings, -WX warnings as errors, -W4 warning level 4, -wdXXXX disable warning XXXX =%^
|
||||
-DHANDMADE_INTERNAL=1 -DHANDMADE_SLOW=1 -DHANDMADE_WIN32=1 %= Custom #defines =%^
|
||||
-FC %= Full path of source code file in diagnostics =%^
|
||||
-Zi %= Generate debugger info =%
|
||||
1
misc/cmds/run.bat
Normal file
1
misc/cmds/run.bat
Normal file
@@ -0,0 +1 @@
|
||||
.\build\handmade_win32.exe
|
||||
2
misc/hh.bat
Normal file
2
misc/hh.bat
Normal file
@@ -0,0 +1,2 @@
|
||||
call .\misc\cmds\%1.bat || exit /b %errorlevel%
|
||||
.\build\handmade_win32.exe
|
||||
294
src/handmade.cpp
294
src/handmade.cpp
@@ -1,112 +1,246 @@
|
||||
#include "handmade.h"
|
||||
#include "handmade_tile.cpp"
|
||||
|
||||
#define PI32 3.141592653589f
|
||||
internal void drawRectangle(GameOffscreenBuffer *buffer, real32 realMinX, real32 realMinY, real32 realMaxX, real32 realMaxY, real32 R, real32 G, real32 B) {
|
||||
int32 minX = roundReal32ToInt32(realMinX);
|
||||
int32 minY = roundReal32ToInt32(realMinY);
|
||||
int32 maxX = roundReal32ToInt32(realMaxX);
|
||||
int32 maxY = roundReal32ToInt32(realMaxY);
|
||||
|
||||
uint32 color = (roundReal32ToUInt32(R*255.0f) << 16) |
|
||||
(roundReal32ToUInt32(G*255.0f) << 8) |
|
||||
(roundReal32ToUInt32(B*255.0f) << 0);
|
||||
|
||||
if (minX < 0) {
|
||||
minX = 0;
|
||||
}
|
||||
|
||||
if (minY < 0) {
|
||||
minY = 0;
|
||||
}
|
||||
|
||||
if (maxX > buffer->width) {
|
||||
maxX = buffer->width;
|
||||
}
|
||||
|
||||
if (maxY > buffer->height) {
|
||||
maxY = buffer->height;
|
||||
}
|
||||
|
||||
internal void renderPlayer(GameOffscreenBuffer *buffer, int playerX, int playerY) {
|
||||
uint8 *endOfBuffer = (uint8 *)buffer->memory + buffer->pitch*buffer->height;
|
||||
uint32 color = 0xFFFFFFFF;
|
||||
int top = playerY;
|
||||
int bottom = playerY + 10;
|
||||
for (int x = playerX; x < playerX + 10; x++) {
|
||||
uint8 *pixel = ((uint8 *)buffer->memory + x * buffer->bytesPerPixel + top*buffer->pitch);
|
||||
for (int y = top; y < bottom; y++) {
|
||||
if ((pixel >= buffer->memory) && (pixel + 4 < endOfBuffer)) {
|
||||
*(uint32 *)pixel = color;
|
||||
}
|
||||
pixel += buffer->pitch;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
internal void outputSineSound(GameSoundOutputBuffer *soundBuffer, GameState *state) {
|
||||
int16 toneVolume = 3000;
|
||||
int wavePeriod = soundBuffer->samplesPerSecond/state->toneHz;
|
||||
|
||||
int16 *sampleOut = soundBuffer->samples;
|
||||
for (int sampleIndex = 0; sampleIndex < soundBuffer->sampleCount; sampleIndex++) {
|
||||
state->tSine += 2.0f * PI32 / (real32)wavePeriod;
|
||||
#if 0
|
||||
int16 sampleValue = (int16)(sin(state->tSine) * (real32)toneVolume);
|
||||
#else
|
||||
int16 sampleValue = 0;
|
||||
#endif
|
||||
*sampleOut++ = sampleValue;
|
||||
*sampleOut++ = sampleValue;
|
||||
}
|
||||
}
|
||||
|
||||
internal void renderWeirdGradient(GameOffscreenBuffer *buffer, int xOffset, int yOffset) {
|
||||
uint8 *row = (uint8 *)buffer->memory;
|
||||
for (int y = 0; y < buffer->height; y++) {
|
||||
uint8 *row = ((uint8 *)buffer->memory + minX*buffer->bytesPerPixel + minY*buffer->pitch);
|
||||
for (int y = minY; y < maxY; y++) {
|
||||
uint32 *pixel = (uint32 *)row;
|
||||
for (int x = 0; x < buffer->width; x++) {
|
||||
uint8 blue = (uint8)(x + xOffset);
|
||||
uint8 green = (uint8)(y + yOffset);
|
||||
*pixel++ = (green << 8) | blue;
|
||||
for (int x = minX; x < maxX; x++) {
|
||||
*pixel++ = color;
|
||||
}
|
||||
row += buffer->pitch;
|
||||
}
|
||||
}
|
||||
|
||||
internal void outputSound(GameSoundOutputBuffer *soundBuffer, GameState *state) {
|
||||
int16 toneVolume = 3000;
|
||||
int16 *sampleOut = soundBuffer->samples;
|
||||
for (int sampleIndex = 0; sampleIndex < soundBuffer->sampleCount; sampleIndex++) {
|
||||
int16 sampleValue = 0;
|
||||
*sampleOut++ = sampleValue;
|
||||
*sampleOut++ = sampleValue;
|
||||
}
|
||||
}
|
||||
|
||||
internal void initialiseArena(MemoryArena *arena, memory_index size, uint8 *base) {
|
||||
arena->size = size;
|
||||
arena->base = base;
|
||||
arena->used = 0;
|
||||
}
|
||||
|
||||
internal void *_pushSize(MemoryArena *arena, memory_index size) {
|
||||
Assert((arena->used + size) <= arena->size);
|
||||
void *result = (void *)(arena->base + arena->used);
|
||||
arena->used += size;
|
||||
return result;
|
||||
}
|
||||
#define pushStruct(arena, type) (type *)_pushSize(arena, sizeof(type))
|
||||
#define pushArray(arena, count, type) (type *)_pushSize(arena, (count)*sizeof(type))
|
||||
|
||||
extern "C" GAME_UPDATE_AND_RENDER(gameUpdateAndRender) {
|
||||
Assert(sizeof(GameState) <= memory->permanentStorageSize);
|
||||
|
||||
GameState *state = (GameState*)memory->permanentStorage;
|
||||
|
||||
const int WORLD_WIDTH = 2;
|
||||
const int WORLD_HEIGHT = 2;
|
||||
|
||||
if (!memory->isInitialised) {
|
||||
DebugReadFileResult bmpMem = memory->debugReadEntireFile(__FILE__);
|
||||
if (bmpMem.contents) {
|
||||
if (false) {
|
||||
memory->debugWriteEntireFile("c:/source/repos/handmade/src/test.cpp", bmpMem.contentsSize, bmpMem.contents);
|
||||
state->playerPos.absTileX = 3;
|
||||
state->playerPos.absTileY = 3;
|
||||
state->playerPos.x = 5.0f;
|
||||
state->playerPos.y = 5.0f;
|
||||
|
||||
initialiseArena(
|
||||
&state->worldArena,
|
||||
memory->permanentStorageSize - sizeof(GameState),
|
||||
(uint8*)memory->permanentStorage + sizeof(GameState)
|
||||
);
|
||||
|
||||
state->world = pushStruct(&state->worldArena, World);
|
||||
World *world = state->world;
|
||||
|
||||
world->tileMap = pushStruct(&state->worldArena, TileMap);
|
||||
TileMap *tileMap = state->world->tileMap;
|
||||
|
||||
tileMap->chunkShift = 8;
|
||||
tileMap->chunkMask = 0xFF;
|
||||
tileMap->chunkDim = 256;
|
||||
tileMap->tileChunkCountX = 4;
|
||||
tileMap->tileChunkCountY = 4;
|
||||
tileMap->tileSideInMeters = 1.4f;
|
||||
tileMap->tileSideInPixels = 60;
|
||||
tileMap->metersToPixels = tileMap->tileSideInPixels / tileMap->tileSideInMeters;
|
||||
|
||||
tileMap->tileChunks = pushArray(&state->worldArena, tileMap->tileChunkCountX*tileMap->tileChunkCountY, TileChunk);
|
||||
|
||||
for (uint32 y = 0; y < tileMap->tileChunkCountY; y++) {
|
||||
for (uint32 x = 0; x < tileMap->tileChunkCountX; x++) {
|
||||
tileMap->tileChunks[y*tileMap->tileChunkCountX + x].tiles = pushArray(&state->worldArena, tileMap->chunkDim*tileMap->chunkDim, uint32);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
uint32 tilesPerWidth = 17;
|
||||
uint32 tilesPerHeight = 9;
|
||||
|
||||
for (uint32 screenY = 0; screenY < 32; screenY++) {
|
||||
for (uint32 screenX = 0; screenX < 32; screenX++) {
|
||||
for (uint32 tileY = 0; tileY < tilesPerHeight; tileY++) {
|
||||
for (uint32 tileX = 0; tileX < tilesPerWidth; tileX++) {
|
||||
uint32 absTileX = screenX*tilesPerWidth + tileX;
|
||||
uint32 absTileY = screenY*tilesPerHeight + tileY;
|
||||
setTileMapValue(&state->worldArena, tileMap, absTileX, absTileY, (tileX == tileY && (tileX % 5 == 0)));
|
||||
}
|
||||
}
|
||||
}
|
||||
memory->debugFreeFileMemory(bmpMem.contents);
|
||||
}
|
||||
|
||||
state->toneHz = 440;
|
||||
state->tSine = 0;
|
||||
state->playerY = 100;
|
||||
state->playerX = 100;
|
||||
state->blueOffset = 0;
|
||||
state->greenOffset = 0;
|
||||
memory->isInitialised = true;
|
||||
}
|
||||
|
||||
real32 screenCenterX = 0.5f*(real32)videoBuf->width;
|
||||
real32 screenCenterY = 0.5f*(real32)videoBuf->height;
|
||||
|
||||
// uint32 tiles[TILEMAP_WIDTH][TILEMAP_HEIGHT] = {
|
||||
// {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
|
||||
// {1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1},
|
||||
// {1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1},
|
||||
// {1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
|
||||
// {1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1},
|
||||
// {1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1},
|
||||
// {1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1},
|
||||
// {1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1},
|
||||
// {1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1},
|
||||
// {1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1},
|
||||
// {1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1},
|
||||
// {1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1},
|
||||
// {1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1},
|
||||
// {1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1},
|
||||
// {1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1},
|
||||
// {1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1},
|
||||
// {1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1},
|
||||
// {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1},
|
||||
// };
|
||||
|
||||
TileMap *tilemap = state->world->tileMap;
|
||||
|
||||
// player
|
||||
real32 playerR = 1.0f;
|
||||
real32 playerG = 1.0f;
|
||||
real32 playerB = 0.5f;
|
||||
real32 playerWidth = 0.7f;
|
||||
real32 playerHeight = 1.4f;
|
||||
|
||||
for (int controllerIndex = 0; controllerIndex < ArrayCount(input->controllers); controllerIndex++) {
|
||||
GameControllerInput *controllerInput = &input->controllers[controllerIndex];
|
||||
/*
|
||||
if (controllerInput->isAnalog) {
|
||||
state->toneHz = 440 + (int)(128.0f*controllerInput->stickAvgX);
|
||||
state->greenOffset -= (int)(20.0f*controllerInput->stickAvgX);
|
||||
state->blueOffset += (int)(20.0f*controllerInput->stickAvgY);
|
||||
} else {
|
||||
if (controllerInput->stickRight.endedDown) {
|
||||
state->toneHz = 440 + 128;
|
||||
state->greenOffset -= 10;
|
||||
} else if (controllerInput->stickLeft.endedDown) {
|
||||
state->toneHz = 440 - 128;
|
||||
state->greenOffset += 10;
|
||||
}
|
||||
if (controllerInput->stickUp.endedDown) {
|
||||
state->blueOffset += 10;
|
||||
} else if (controllerInput->stickDown.endedDown) {
|
||||
state->blueOffset -= 10;
|
||||
}
|
||||
}
|
||||
*/
|
||||
GameControllerInput *controller = &input->controllers[controllerIndex];
|
||||
|
||||
if (controllerInput->btnDown.endedDown) {
|
||||
state->playerY -= 30;
|
||||
real32 dPlayerX = 0.0f;
|
||||
real32 dPlayerY = 0.0f;
|
||||
|
||||
if (controller->stickUp.endedDown) {
|
||||
dPlayerY = 1.0f;
|
||||
}
|
||||
if (controller->stickDown.endedDown) {
|
||||
dPlayerY = -1.0f;
|
||||
}
|
||||
if (controller->stickLeft.endedDown) {
|
||||
dPlayerX = -1.0f;
|
||||
}
|
||||
if (controller->stickRight.endedDown) {
|
||||
dPlayerX = 1.0f;
|
||||
}
|
||||
|
||||
state->playerX += (int)(4.0f*controllerInput->stickAvgX);
|
||||
state->playerY -= (int)(4.0f*controllerInput->stickAvgY);
|
||||
dPlayerX *= 6.0f;
|
||||
dPlayerY *= 6.0f;
|
||||
|
||||
TileMapPosition newPlayerPos = state->playerPos;
|
||||
newPlayerPos.x += dPlayerX * input->dtForFrame;
|
||||
newPlayerPos.y += dPlayerY * input->dtForFrame;
|
||||
newPlayerPos = recanonicalisePosition(tilemap, newPlayerPos);
|
||||
|
||||
TileMapPosition playerBottomLeft = newPlayerPos;
|
||||
playerBottomLeft.x -= 0.5f * playerWidth;
|
||||
playerBottomLeft = recanonicalisePosition(tilemap, playerBottomLeft);
|
||||
|
||||
TileMapPosition playerTopLeft = newPlayerPos;
|
||||
playerTopLeft.x -= 0.5f * playerWidth;
|
||||
playerTopLeft.y += playerWidth;
|
||||
playerTopLeft = recanonicalisePosition(tilemap, playerTopLeft);
|
||||
|
||||
TileMapPosition playerBottomRight = newPlayerPos;
|
||||
playerBottomRight.x += 0.5f * playerWidth;
|
||||
playerBottomRight = recanonicalisePosition(tilemap, playerBottomRight);
|
||||
|
||||
TileMapPosition playerTopRight = newPlayerPos;
|
||||
playerTopRight.x += 0.5f * playerWidth;
|
||||
playerTopRight.y += playerWidth;
|
||||
playerTopRight = recanonicalisePosition(tilemap, playerTopRight);
|
||||
|
||||
if (
|
||||
isTileMapPointEmpty(tilemap, playerTopLeft) &&
|
||||
isTileMapPointEmpty(tilemap, playerTopRight) &&
|
||||
isTileMapPointEmpty(tilemap, playerBottomLeft) &&
|
||||
isTileMapPointEmpty(tilemap, playerBottomRight)
|
||||
) {
|
||||
state->playerPos = newPlayerPos;
|
||||
}
|
||||
}
|
||||
|
||||
renderWeirdGradient(videoBuf, state->greenOffset, state->blueOffset);
|
||||
renderPlayer(videoBuf, state->playerX, state->playerY);
|
||||
// clearscreen
|
||||
drawRectangle(videoBuf, 0.0f, 0.0f, (real32)videoBuf->width, (real32)videoBuf->height, 1.0f, 0.0f, 1.0f);
|
||||
|
||||
for (int32 relRow = -10; relRow < 10; relRow++) {
|
||||
for (int32 relCol = -20; relCol < 20; relCol++) {
|
||||
uint32 col = state->playerPos.absTileX + relCol;
|
||||
uint32 row = state->playerPos.absTileY + relRow;
|
||||
int32 tileId = getTileValue(tilemap, col, row);
|
||||
real32 fill = 0.5f;
|
||||
if (tileId == 1) {
|
||||
fill = 1.0f;
|
||||
}
|
||||
if ((row == state->playerPos.absTileY) && (col == state->playerPos.absTileX)) {
|
||||
fill = 0.0f;
|
||||
}
|
||||
real32 minX = screenCenterX - tilemap->metersToPixels*state->playerPos.x + ((real32)relCol)*tilemap->tileSideInPixels;
|
||||
real32 minY = screenCenterY + tilemap->metersToPixels*state->playerPos.y - ((real32)relRow)*tilemap->tileSideInPixels;
|
||||
real32 maxX = minX + tilemap->tileSideInPixels;
|
||||
real32 maxY = minY - tilemap->tileSideInPixels;
|
||||
drawRectangle(videoBuf, minX, maxY, maxX, minY, fill, fill, fill);
|
||||
}
|
||||
}
|
||||
|
||||
real32 playerLeft = screenCenterX - 0.5f*tilemap->metersToPixels*playerWidth;
|
||||
real32 playerTop = screenCenterY - tilemap->metersToPixels*playerHeight;
|
||||
drawRectangle(videoBuf, playerLeft, playerTop, playerLeft + tilemap->metersToPixels*playerWidth, playerTop + tilemap->metersToPixels*playerHeight, playerR, playerG, playerB);
|
||||
}
|
||||
|
||||
extern "C" GAME_GET_SOUND_SAMPLES(gameGetSoundSamples) {
|
||||
outputSineSound(soundBuf, (GameState*)memory->permanentStorage);
|
||||
outputSound(soundBuf, (GameState*)memory->permanentStorage);
|
||||
}
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <math.h>
|
||||
#include <windows.h>
|
||||
|
||||
#pragma once
|
||||
|
||||
/*
|
||||
* ---------------
|
||||
@@ -47,11 +45,15 @@ typedef uint16_t uint16;
|
||||
typedef uint32_t uint32;
|
||||
typedef uint64_t uint64;
|
||||
|
||||
typedef size_t MemoryIndex;
|
||||
|
||||
typedef int8_t int8;
|
||||
typedef int16_t int16;
|
||||
typedef int32_t int32;
|
||||
typedef int64_t int64;
|
||||
|
||||
typedef size_t memory_index;
|
||||
|
||||
typedef float real32;
|
||||
typedef double real64;
|
||||
|
||||
@@ -64,6 +66,10 @@ inline uint32 safeTruncateUInt64(uint64 val) {
|
||||
return (uint32)val;
|
||||
}
|
||||
|
||||
struct ThreadContext {
|
||||
int placeholder;
|
||||
};
|
||||
|
||||
// === Platform to game services ===
|
||||
#if HANDMADE_INTERNAL
|
||||
|
||||
@@ -72,13 +78,13 @@ struct DebugReadFileResult {
|
||||
void *contents;
|
||||
};
|
||||
|
||||
#define DEBUG_PLATFORM_READ_ENTIRE_FILE(name) DebugReadFileResult name(char *filename)
|
||||
#define DEBUG_PLATFORM_READ_ENTIRE_FILE(name) DebugReadFileResult name(ThreadContext *ctx, char *filename)
|
||||
typedef DEBUG_PLATFORM_READ_ENTIRE_FILE(DebugPlatformReadEntireFileFn);
|
||||
|
||||
#define DEBUG_PLATFORM_FREE_FILE_MEMORY(name) void name(void *fileMemory)
|
||||
#define DEBUG_PLATFORM_FREE_FILE_MEMORY(name) void name(ThreadContext *ctx, void *fileMemory)
|
||||
typedef DEBUG_PLATFORM_FREE_FILE_MEMORY(DebugPlatformFreeFileMemoryFn);
|
||||
|
||||
#define DEBUG_PLATFORM_WRITE_ENTIRE_FILE(name) bool32 name(char *filename, uint32 memorySize, void *memory)
|
||||
#define DEBUG_PLATFORM_WRITE_ENTIRE_FILE(name) bool32 name(ThreadContext *ctx, char *filename, uint32 memorySize, void *memory)
|
||||
typedef DEBUG_PLATFORM_WRITE_ENTIRE_FILE(DebugPlatformWriteEntireFileFn);
|
||||
|
||||
#define DEBUG_PLATFORM_PRINTF(name) void name(wchar_t* format, ...)
|
||||
@@ -130,39 +136,51 @@ struct GameControllerInput {
|
||||
};
|
||||
|
||||
struct GameInput {
|
||||
GameButtonState mouseButtons[5];
|
||||
int32 mouseX;
|
||||
int32 mouseY;
|
||||
int32 mouseZ;
|
||||
real32 dtForFrame;
|
||||
GameControllerInput controllers[5];
|
||||
};
|
||||
|
||||
struct GameMemory {
|
||||
bool32 isInitialised;
|
||||
|
||||
uint64 permanentStorageSize;
|
||||
void *permanentStorage; // required to be initialised to zero at startup
|
||||
|
||||
uint64 transientStorageSize;
|
||||
void *transientStorage; // required to be initialised to zero at startup
|
||||
|
||||
DebugPlatformReadEntireFileFn *debugReadEntireFile;
|
||||
DebugPlatformFreeFileMemoryFn *debugFreeFileMemory;
|
||||
DebugPlatformWriteEntireFileFn *debugWriteEntireFile;
|
||||
DebugPrintfFn *debug_printf;
|
||||
};
|
||||
|
||||
#include "handmade_tile.h"
|
||||
#include "handmade_intrinsics.h"
|
||||
|
||||
struct World {
|
||||
TileMap *tileMap;
|
||||
};
|
||||
|
||||
struct MemoryArena {
|
||||
memory_index size;
|
||||
uint8 *base;
|
||||
memory_index used;
|
||||
};
|
||||
|
||||
struct GameState {
|
||||
int toneHz;
|
||||
int greenOffset;
|
||||
int blueOffset;
|
||||
real32 tSine;
|
||||
int playerY;
|
||||
int playerX;
|
||||
TileMapPosition playerPos;
|
||||
World *world;
|
||||
MemoryArena worldArena;
|
||||
};
|
||||
|
||||
// === Game to platform services ===
|
||||
|
||||
#define GAME_UPDATE_AND_RENDER(name) void name(GameMemory *memory, GameOffscreenBuffer *videoBuf, GameInput *input)
|
||||
#define GAME_UPDATE_AND_RENDER(name) void name(ThreadContext *ctx, GameMemory *memory, GameOffscreenBuffer *videoBuf, GameInput *input)
|
||||
typedef GAME_UPDATE_AND_RENDER(GameUpdateAndRenderFn);
|
||||
GAME_UPDATE_AND_RENDER(gameUpdateAndRenderStub) {}
|
||||
|
||||
#define GAME_GET_SOUND_SAMPLES(name) void name(GameMemory *memory, GameSoundOutputBuffer *soundBuf)
|
||||
#define GAME_GET_SOUND_SAMPLES(name) void name(ThreadContext *ctx, GameMemory *memory, GameSoundOutputBuffer *soundBuf)
|
||||
typedef GAME_GET_SOUND_SAMPLES(GameGetSoundSamplesFn);
|
||||
GAME_GET_SOUND_SAMPLES(gameGetSoundSamplesStub) {}
|
||||
|
||||
37
src/handmade_intrinsics.h
Normal file
37
src/handmade_intrinsics.h
Normal file
@@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
#include "handmade.h"
|
||||
#include "math.h"
|
||||
|
||||
#define PI32 3.141592653589f
|
||||
|
||||
inline int32 roundReal32ToUInt32(real32 realNum) {
|
||||
return (uint32)(realNum + 0.5f);
|
||||
}
|
||||
|
||||
inline int32 roundReal32ToInt32(real32 realNum) {
|
||||
return (int32)(realNum + 0.5f);
|
||||
}
|
||||
|
||||
inline int32 floorReal32ToInt32(real32 realNum) {
|
||||
return (int32)floorf(realNum);
|
||||
}
|
||||
|
||||
inline int32 truncateReal32ToInt32(real32 realNum) {
|
||||
return (int32)realNum;
|
||||
}
|
||||
|
||||
inline real32 sin(real32 angle) {
|
||||
return sinf(angle);
|
||||
}
|
||||
|
||||
inline real32 cos(real32 angle) {
|
||||
return cosf(angle);
|
||||
}
|
||||
|
||||
inline real32 atan2(real32 y, real32 x) {
|
||||
return atan2f(y, x);
|
||||
}
|
||||
|
||||
inline uint32 absoluteInt32ToUInt32(int32 signedInt32) {
|
||||
return signedInt32 > 0 ? (uint32)signedInt32 : (uint32)(-1*signedInt32);
|
||||
}
|
||||
95
src/handmade_tile.cpp
Normal file
95
src/handmade_tile.cpp
Normal file
@@ -0,0 +1,95 @@
|
||||
#pragma once
|
||||
#include "handmade.h"
|
||||
|
||||
inline TileChunk *getTileChunk(TileMap *tileMap, uint32 tileChunkX, uint32 tileChunkY) {
|
||||
TileChunk *tileChunk = NULL;
|
||||
|
||||
if ((tileChunkX >= 0) && (tileChunkX < tileMap->tileChunkCountX) &&
|
||||
(tileChunkY >= 0) && (tileChunkY < tileMap->tileChunkCountY)
|
||||
) {
|
||||
tileChunk = &tileMap->tileChunks[tileChunkY * tileMap->tileChunkCountX + tileChunkX];
|
||||
}
|
||||
|
||||
return tileChunk;
|
||||
}
|
||||
|
||||
inline void recanonicaliseOrd(TileMap *tileMap, uint32 *tileOrd, real32* ord) {
|
||||
int32 offset = floorReal32ToInt32(*ord / tileMap->tileSideInMeters);
|
||||
|
||||
*tileOrd += offset;
|
||||
*ord -= offset*tileMap->tileSideInMeters;
|
||||
|
||||
Assert(*ord >= 0.0f);
|
||||
Assert(*ord < tileMap->tileSideInMeters);
|
||||
}
|
||||
|
||||
inline TileChunkPosition getChunkPosition(TileMap* tileMap, uint32 absTileX, uint32 absTileY) {
|
||||
TileChunkPosition result = {};
|
||||
result.tileChunkX = absTileX >> tileMap->chunkShift;
|
||||
result.tileChunkY = absTileY >> tileMap->chunkShift;
|
||||
result.relTileX = absTileX & tileMap->chunkMask;
|
||||
result.relTileY = absTileY & tileMap->chunkMask;
|
||||
return result;
|
||||
}
|
||||
|
||||
inline uint32 getTileValueUnchecked(TileMap *tileMap, TileChunk *tileChunk, uint32 testTileX, uint32 testTileY) {
|
||||
Assert(tileChunk);
|
||||
Assert(testTileX < tileMap->chunkDim);
|
||||
Assert(testTileY < tileMap->chunkDim);
|
||||
return tileChunk->tiles[testTileY * tileMap->chunkDim + testTileX];
|
||||
}
|
||||
|
||||
inline void setTileValueUnchecked(TileMap *tileMap, TileChunk *tileChunk, uint32 testTileX, uint32 testTileY, uint32 tileValue) {
|
||||
Assert(tileChunk);
|
||||
Assert(testTileX < tileMap->chunkDim);
|
||||
Assert(testTileY < tileMap->chunkDim);
|
||||
tileChunk->tiles[testTileY * tileMap->chunkDim + testTileX] = tileValue;
|
||||
}
|
||||
|
||||
inline uint32 getTileValue(TileMap *tileMap, TileChunk* tileChunk, uint32 testTileX, uint32 testTileY) {
|
||||
uint32 tileValue = 0;
|
||||
if (tileChunk) {
|
||||
tileValue = getTileValueUnchecked(tileMap, tileChunk, testTileX, testTileY);
|
||||
}
|
||||
return tileValue;
|
||||
}
|
||||
|
||||
inline void setTileValue(TileMap *tileMap, TileChunk* tileChunk, uint32 testTileX, uint32 testTileY, uint32 tileValue) {
|
||||
if (tileChunk) {
|
||||
setTileValueUnchecked(tileMap, tileChunk, testTileX, testTileY, tileValue);
|
||||
}
|
||||
}
|
||||
|
||||
inline uint32 getTileValue(TileMap *tileMap, uint32 testTileX, uint32 testTileY) {
|
||||
uint32 tileValue = 0;
|
||||
TileChunkPosition chunkPos = getChunkPosition(tileMap, testTileX, testTileY);
|
||||
TileChunk *tileChunk = getTileChunk(tileMap, chunkPos.tileChunkX, chunkPos.tileChunkY);
|
||||
tileValue = getTileValue(tileMap, tileChunk, testTileX, testTileY);
|
||||
return tileValue;
|
||||
}
|
||||
|
||||
inline TileMapPosition recanonicalisePosition(TileMap *tileMap, TileMapPosition pos) {
|
||||
TileMapPosition result = pos;
|
||||
|
||||
recanonicaliseOrd(tileMap, &result.absTileX, &result.x);
|
||||
recanonicaliseOrd(tileMap, &result.absTileY, &result.y);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
internal bool32 isTileMapPointEmpty(TileMap *tileMap, TileMapPosition testPos) {
|
||||
bool32 isEmpty = false;
|
||||
|
||||
TileChunkPosition chunkPos = getChunkPosition(tileMap, testPos.absTileX, testPos.absTileY);
|
||||
TileChunk *tileChunk = getTileChunk(tileMap, chunkPos.tileChunkX, chunkPos.tileChunkY);
|
||||
isEmpty = (getTileValue(tileMap, tileChunk, chunkPos.relTileX, chunkPos.relTileY) == 0);
|
||||
|
||||
return isEmpty;
|
||||
}
|
||||
|
||||
internal void setTileMapValue(MemoryArena *arena, TileMap *tileMap, uint32 absTileX, uint32 absTileY, uint32 tileValue) {
|
||||
TileChunkPosition chunkPos = getChunkPosition(tileMap, absTileX, absTileY);
|
||||
TileChunk *tileChunk = getTileChunk(tileMap, chunkPos.tileChunkX, chunkPos.tileChunkY);
|
||||
Assert(tileChunk);
|
||||
setTileValue(tileMap, tileChunk, chunkPos.relTileX, chunkPos.relTileY, tileValue);
|
||||
}
|
||||
41
src/handmade_tile.h
Normal file
41
src/handmade_tile.h
Normal file
@@ -0,0 +1,41 @@
|
||||
#pragma once
|
||||
#include "handmade.h"
|
||||
|
||||
struct TileChunk {
|
||||
uint32 *tiles;
|
||||
};
|
||||
|
||||
struct TileChunkPosition {
|
||||
uint32 tileChunkX;
|
||||
uint32 tileChunkY;
|
||||
|
||||
uint32 relTileX;
|
||||
uint32 relTileY;
|
||||
};
|
||||
|
||||
struct TileMapPosition {
|
||||
// tile map x and y and tile x and y packed, lower 8 bits are tile X,Y and upper 24 bits map X,Y
|
||||
// ---- ---- ---- ---- ---- ---- : ---- ----
|
||||
// TILE MAP ORD : TILE ORD
|
||||
uint32 absTileX;
|
||||
uint32 absTileY;
|
||||
|
||||
// Sub-tile position
|
||||
real32 x;
|
||||
real32 y;
|
||||
};
|
||||
|
||||
struct TileMap {
|
||||
uint32 chunkShift;
|
||||
uint32 chunkMask;
|
||||
uint32 chunkDim;
|
||||
|
||||
real32 tileSideInMeters;
|
||||
int32 tileSideInPixels;
|
||||
real32 metersToPixels;
|
||||
|
||||
uint32 tileChunkCountX;
|
||||
uint32 tileChunkCountY;
|
||||
|
||||
TileChunk *tileChunks;
|
||||
};
|
||||
@@ -60,7 +60,7 @@ DEBUG_PLATFORM_READ_ENTIRE_FILE(debugReadEntireFile) {
|
||||
if (ReadFile(fileHandle, result.contents, (DWORD)fileSize.QuadPart, &bytesRead, NULL) && (fileSize32 == (uint32)bytesRead)) {
|
||||
result.contentsSize = fileSize32;
|
||||
} else {
|
||||
debugFreeFileMemory(result.contents);
|
||||
debugFreeFileMemory(ctx, result.contents);
|
||||
result.contents = NULL;
|
||||
// logging
|
||||
}
|
||||
@@ -128,9 +128,18 @@ internal Win32WindowDimensions win32GetWindowDimensions(HWND window) {
|
||||
|
||||
internal void win32DrawBufferInWindow(Win32OffscreenBuffer *buffer, HWND window) {
|
||||
Win32WindowDimensions winDims = win32GetWindowDimensions(window);
|
||||
StretchDIBits(
|
||||
GetDC(window),
|
||||
0, 0, winDims.width, winDims.height,
|
||||
HDC dc = GetDC(window);
|
||||
|
||||
int offsetX = 10;
|
||||
int offsetY = 10;
|
||||
|
||||
PatBlt(dc, 0, 0, winDims.width, offsetY, BLACKNESS);
|
||||
PatBlt(dc, 0, offsetY + buffer->height, winDims.width, winDims.height, BLACKNESS);
|
||||
PatBlt(dc, 0, 0, offsetX, winDims.height, BLACKNESS);
|
||||
PatBlt(dc, offsetX + buffer->width, 0, winDims.width, winDims.height, BLACKNESS);
|
||||
|
||||
StretchDIBits(dc,
|
||||
offsetX, offsetY, buffer->width, buffer->height,
|
||||
0, 0, buffer->width, buffer->height,
|
||||
buffer->memory,
|
||||
&buffer->info,
|
||||
@@ -139,14 +148,6 @@ internal void win32DrawBufferInWindow(Win32OffscreenBuffer *buffer, HWND window)
|
||||
);
|
||||
}
|
||||
|
||||
struct Win32GameCode {
|
||||
HMODULE gameCodeLib;
|
||||
GameUpdateAndRenderFn *updateAndRender;
|
||||
GameGetSoundSamplesFn *getSoundSamples;
|
||||
bool isValid;
|
||||
FILETIME lastWriteTime;
|
||||
};
|
||||
|
||||
inline FILETIME win32GetLastWriteTime(char *filename) {
|
||||
FILETIME lastWriteTime = {};
|
||||
WIN32_FIND_DATA findData;
|
||||
@@ -202,9 +203,11 @@ internal void win32LoadXInput() {
|
||||
}
|
||||
|
||||
internal void win32ProcessKeyboardKeypress(GameButtonState *newState, bool32 isDown) {
|
||||
if (newState->endedDown != isDown) {
|
||||
newState->endedDown = isDown;
|
||||
newState->halfTransitionCount++;
|
||||
}
|
||||
}
|
||||
|
||||
internal void win32ProcessXInputDigitalButton(DWORD xInputButtonState, GameButtonState *oldState, GameButtonState *newState, DWORD buttonBit) {
|
||||
newState->endedDown = (xInputButtonState & buttonBit) == buttonBit;
|
||||
@@ -258,12 +261,7 @@ internal void win32InitSound(HWND window, int32 samplesPerSec, int bufferSize) {
|
||||
}
|
||||
}
|
||||
|
||||
LRESULT mainWindowCallback(
|
||||
HWND window,
|
||||
UINT message,
|
||||
WPARAM wParam,
|
||||
LPARAM lParam
|
||||
) {
|
||||
LRESULT mainWindowCallback(HWND window, UINT message, WPARAM wParam, LPARAM lParam) {
|
||||
LRESULT result = 0;
|
||||
switch (message) {
|
||||
case WM_SIZE: {
|
||||
@@ -360,18 +358,24 @@ internal void win32FillSoundBuffer(Win32SoundOutput *soundOutput, DWORD byteToLo
|
||||
}
|
||||
}
|
||||
|
||||
internal void win32BeginRecordingInput(Win32State *win32State, int inputRecordingIndex) {
|
||||
win32State->inputRecordingIndex = inputRecordingIndex;
|
||||
char *filename = "replay.ipt";
|
||||
win32State->recordingHandle = CreateFileA(filename, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, NULL, NULL);
|
||||
DWORD bytesToWrite = (DWORD)win32State->gameMemoryTotalSize;
|
||||
Assert(bytesToWrite < 0xFFFFFFFF);
|
||||
DWORD bytesWritten;
|
||||
WriteFile(win32State->recordingHandle, win32State->gameMemoryBlock, bytesToWrite, &bytesWritten, NULL);
|
||||
internal Win32ReplayBuffer* win32GetReplayBuffer(Win32State *state, int unsigned index) {
|
||||
Win32ReplayBuffer *result = &state->replayBuffers[index - 1];
|
||||
return result;
|
||||
}
|
||||
|
||||
internal void win32BeginRecordingInput(Win32State *state, int inputRecordingIndex) {
|
||||
Win32ReplayBuffer *replayBuffer = win32GetReplayBuffer(state, inputRecordingIndex);
|
||||
if (replayBuffer->memoryBlock) {
|
||||
state->inputRecordingIndex = inputRecordingIndex;
|
||||
state->recordingHandle = replayBuffer->fileHandle;
|
||||
LARGE_INTEGER filePosition;
|
||||
filePosition.QuadPart = state->gameMemoryTotalSize;
|
||||
SetFilePointerEx(state->recordingHandle, filePosition, NULL, FILE_BEGIN);
|
||||
CopyMemory(replayBuffer->memoryBlock, state->gameMemoryBlock, state->gameMemoryTotalSize);
|
||||
}
|
||||
}
|
||||
|
||||
internal void win32EndRecordingInput(Win32State *win32State) {
|
||||
CloseHandle(win32State->recordingHandle);
|
||||
win32State->inputRecordingIndex = 0;
|
||||
}
|
||||
|
||||
@@ -380,18 +384,19 @@ internal void win32RecordInput(Win32State *win32State, GameInput *newInput) {
|
||||
WriteFile(win32State->recordingHandle, newInput, sizeof(*newInput), &bytesWritten, NULL);
|
||||
}
|
||||
|
||||
internal void win32BeginInputPlayback(Win32State *win32State, int inputPlayingIndex) {
|
||||
win32State->inputPlayingIndex = inputPlayingIndex;
|
||||
char *filename = "replay.ipt";
|
||||
win32State->playbackHandle = CreateFileA(filename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, NULL, NULL);
|
||||
DWORD bytesToRead = (DWORD)win32State->gameMemoryTotalSize;
|
||||
Assert(bytesToRead < 0xFFFFFFFF);
|
||||
DWORD bytesRead;
|
||||
ReadFile(win32State->playbackHandle, win32State->gameMemoryBlock, bytesToRead, &bytesRead, NULL);
|
||||
internal void win32BeginInputPlayback(Win32State *state, int inputPlayingIndex) {
|
||||
Win32ReplayBuffer *replayBuffer = win32GetReplayBuffer(state, inputPlayingIndex);
|
||||
if (replayBuffer->memoryBlock) {
|
||||
state->inputPlayingIndex = inputPlayingIndex;
|
||||
state->playbackHandle = replayBuffer->fileHandle;
|
||||
LARGE_INTEGER filePosition;
|
||||
filePosition.QuadPart = state->gameMemoryTotalSize;
|
||||
SetFilePointerEx(state->playbackHandle, filePosition, NULL, FILE_BEGIN);
|
||||
CopyMemory(state->gameMemoryBlock, replayBuffer->memoryBlock, state->gameMemoryTotalSize);
|
||||
}
|
||||
}
|
||||
|
||||
internal void win32EndInputPlayback(Win32State *win32State) {
|
||||
CloseHandle(win32State->playbackHandle);
|
||||
win32State->inputPlayingIndex = 0;
|
||||
}
|
||||
|
||||
@@ -449,11 +454,13 @@ internal void win32ProcessPendingMessages(Win32State *win32State, GameController
|
||||
} else if (VKCode == VK_SPACE) {
|
||||
} else if (VKCode == 'L') {
|
||||
if (isDown) {
|
||||
if (win32State->inputRecordingIndex == 0) {
|
||||
if (win32State->inputRecordingIndex == 0 && win32State->inputPlayingIndex == 0) {
|
||||
win32BeginRecordingInput(win32State, 1);
|
||||
} else {
|
||||
} else if (win32State->inputRecordingIndex != 0 && win32State->inputPlayingIndex == 0) {
|
||||
win32EndRecordingInput(win32State);
|
||||
win32BeginInputPlayback(win32State, 1);
|
||||
} else if (win32State->inputRecordingIndex == 0 && win32State->inputPlayingIndex != 0) {
|
||||
win32EndInputPlayback(win32State);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -483,12 +490,6 @@ inline real32 win32GetSecondsElapsed(LARGE_INTEGER start, LARGE_INTEGER end) {
|
||||
return (real32)(end.QuadPart - start.QuadPart) / (real32)globalPerfCountFrequency;
|
||||
}
|
||||
|
||||
// int monitorRefreshHz = 60;
|
||||
// int gameUpdateHz = monitorRefreshHz / 2;
|
||||
|
||||
#define monitorRefreshHz 60
|
||||
#define gameUpdateHz (monitorRefreshHz / 2)
|
||||
|
||||
internal void win32DebugDrawVertical(Win32OffscreenBuffer *buffer, int x, int top, int bottom, int32 color) {
|
||||
uint8 *pixel = (uint8 *)buffer->memory + top * buffer->pitch + x*buffer->bytesPerPixel;
|
||||
for (int y = top; y < bottom; y++) {
|
||||
@@ -503,29 +504,6 @@ inline void win32DrawSoundBufferMarker(Win32OffscreenBuffer *buffer, Win32SoundO
|
||||
win32DebugDrawVertical(buffer, x, top, bottom, color);
|
||||
}
|
||||
|
||||
internal void win32DebugSyncDisplay(Win32OffscreenBuffer *buffer, int markerCount, Win32DebugTimeMarker *markers, Win32SoundOutput *soundOutput, real32 targetSecondsPerFrame) {
|
||||
int padX = 16;
|
||||
int padY = 16;
|
||||
|
||||
int top = padY;
|
||||
int bottom = buffer->height - padY;
|
||||
int renderWidth = buffer->width - 2 *padX;
|
||||
|
||||
real32 pxPerSoundBufferEntry = (real32)renderWidth / (real32)soundOutput->secondaryBufferSize;
|
||||
|
||||
#if 0
|
||||
for (int markerIndex = 0; markerIndex < markerCount; markerIndex++) {
|
||||
Win32DebugTimeMarker *thisMarker = &markers[markerIndex];
|
||||
real32 alpha = ((real32)(markerIndex + 1) / (real32)markerCount);
|
||||
int x = padX + (int)(pxPerSoundBufferEntry * (real32)thisMarker->writeCursor);
|
||||
uint8 *pixel = (uint8 *)buffer->memory + top * buffer->pitch + x*buffer->bytesPerPixel;
|
||||
uint32 newPixel = (uint32)((real32)alpha * 0xFFFFFFFF + (1.0f - alpha) * (*(uint32 *)pixel));
|
||||
win32DrawSoundBufferMarker(buffer, soundOutput, pxPerSoundBufferEntry, padX, top, bottom, thisMarker->playCursor, newPixel);
|
||||
win32DrawSoundBufferMarker(buffer, soundOutput, pxPerSoundBufferEntry, padX, top, bottom, thisMarker->writeCursor, 0x00FF0000);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void catStrings(size_t sourceACount, char *sourceA, size_t sourceBCount, char *sourceB, size_t destCount, char *dest) {
|
||||
for (int i = 0; i < sourceACount; i++) {
|
||||
*dest++ = *sourceA++;
|
||||
@@ -536,27 +514,34 @@ void catStrings(size_t sourceACount, char *sourceA, size_t sourceBCount, char *s
|
||||
*dest++ = 0;
|
||||
}
|
||||
|
||||
int APIENTRY WinMain(HINSTANCE instance, HINSTANCE prevInstance, PSTR commandLine, int commandShow) {
|
||||
char exeFileName[MAX_PATH];
|
||||
DWORD sizeOfFilename = GetModuleFileNameA(NULL, exeFileName, sizeof(exeFileName));
|
||||
char *onePastLastSlash = exeFileName;
|
||||
for (char *scan = exeFileName; *scan; scan++) {
|
||||
void win32GetExeFilename(Win32State *win32State) {
|
||||
DWORD sizeOfFilename = GetModuleFileNameA(NULL, win32State->exeFilename, sizeof(win32State->exeFilename));
|
||||
win32State->onePastLastExeFilenameSlash = win32State->exeFilename;
|
||||
for (char *scan = win32State->exeFilename; *scan; scan++) {
|
||||
if (*scan == '\\') {
|
||||
onePastLastSlash = scan + 1;
|
||||
win32State->onePastLastExeFilenameSlash = scan + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
char sourceGameCodeDLLFilename[] = "handmade.dll";
|
||||
void win32GetFullPathToLocalFile(Win32State *state, char *filename, int filenameSize, char *maxPathBuffer) {
|
||||
catStrings(state->onePastLastExeFilenameSlash - state->exeFilename, state->exeFilename, filenameSize, filename, (size_t)MAX_PATH, maxPathBuffer);
|
||||
}
|
||||
|
||||
#define WINDOW_WIDTH 960
|
||||
#define WINDOW_HEIGHT 540
|
||||
|
||||
int APIENTRY WinMain(HINSTANCE instance, HINSTANCE prevInstance, PSTR commandLine, int commandShow) {
|
||||
Win32State win32State = {};
|
||||
win32GetExeFilename(&win32State);
|
||||
|
||||
char *sourceGameCodeDLLFilename = "handmade.dll";
|
||||
char sourceGameCodeDLLFullPath[MAX_PATH];
|
||||
catStrings(onePastLastSlash - exeFileName, exeFileName,
|
||||
sizeof(sourceGameCodeDLLFilename) - 1, sourceGameCodeDLLFilename,
|
||||
sizeof(sourceGameCodeDLLFullPath) - 1, sourceGameCodeDLLFullPath);
|
||||
win32GetFullPathToLocalFile(&win32State, sourceGameCodeDLLFilename, sizeof(sourceGameCodeDLLFullPath) - 1, sourceGameCodeDLLFullPath);
|
||||
|
||||
char tempGameCodeDLLFilename[] = "handmade_temp.dll";
|
||||
char tempGameCodeDLLFullPath[MAX_PATH];
|
||||
catStrings(onePastLastSlash - exeFileName, exeFileName,
|
||||
sizeof(tempGameCodeDLLFilename) - 1, tempGameCodeDLLFilename,
|
||||
sizeof(tempGameCodeDLLFullPath) - 1, tempGameCodeDLLFullPath);
|
||||
win32GetFullPathToLocalFile(&win32State, tempGameCodeDLLFilename, sizeof(tempGameCodeDLLFullPath) - 1, tempGameCodeDLLFullPath);
|
||||
|
||||
LARGE_INTEGER performanceFrequencyResult;
|
||||
QueryPerformanceFrequency(&performanceFrequencyResult);
|
||||
@@ -570,40 +555,45 @@ int APIENTRY WinMain(HINSTANCE instance, HINSTANCE prevInstance, PSTR commandLin
|
||||
windowClass.lpfnWndProc = &mainWindowCallback;
|
||||
windowClass.hInstance = instance;
|
||||
windowClass.lpszClassName = "HandmadeHeroWindowClass";
|
||||
|
||||
resizeDIBSection(&globalBackBuffer, 1280, 720);
|
||||
|
||||
real32 targetSecondsPerFrame = 1.0f / (real32)gameUpdateHz;
|
||||
resizeDIBSection(&globalBackBuffer, WINDOW_WIDTH, WINDOW_HEIGHT);
|
||||
|
||||
if (RegisterClass(&windowClass)) {
|
||||
DWORD windowFlags = WS_OVERLAPPEDWINDOW | WS_VISIBLE;
|
||||
RECT rect = { 0, 0, WINDOW_WIDTH, WINDOW_HEIGHT };
|
||||
AdjustWindowRectEx(&rect, windowFlags, false, NULL);
|
||||
HWND window = CreateWindowExA(
|
||||
NULL,
|
||||
windowClass.lpszClassName,
|
||||
"Handmade Hero",
|
||||
WS_OVERLAPPEDWINDOW | WS_VISIBLE,
|
||||
CW_USEDEFAULT,
|
||||
CW_USEDEFAULT,
|
||||
windowFlags,
|
||||
CW_USEDEFAULT,
|
||||
CW_USEDEFAULT,
|
||||
rect.right - rect.left,
|
||||
rect.bottom - rect.top,
|
||||
NULL,
|
||||
NULL,
|
||||
instance,
|
||||
NULL
|
||||
);
|
||||
|
||||
if (window) {
|
||||
int monitorRefreshHz = 60;
|
||||
int win32RefreshRate = GetDeviceCaps(GetDC(window), VREFRESH);
|
||||
if (win32RefreshRate > 1) {
|
||||
monitorRefreshHz = win32RefreshRate;
|
||||
}
|
||||
real32 gameUpdateHz = monitorRefreshHz / 2.0f;
|
||||
real32 targetSecondsPerFrame = 1.0f / (real32)gameUpdateHz;
|
||||
|
||||
HH_CTRLW = GlobalAddAtomA("HH_CTRLW");
|
||||
RegisterHotKey(window, HH_CTRLW, MOD_CONTROL, 'W');
|
||||
|
||||
globalRunning = true;
|
||||
Win32State win32State = {};
|
||||
|
||||
GameInput input[2] = {};
|
||||
GameInput *oldInput = &input[0];
|
||||
GameInput *newInput = &input[1];
|
||||
|
||||
int debugTimeMarkerIndex = 0;
|
||||
Win32DebugTimeMarker debugMarkers[gameUpdateHz / 2] = {};
|
||||
|
||||
win32LoadXInput();
|
||||
|
||||
// sound test
|
||||
@@ -612,7 +602,7 @@ int APIENTRY WinMain(HINSTANCE instance, HINSTANCE prevInstance, PSTR commandLin
|
||||
soundOutput.runningSampleIndex = 0;
|
||||
soundOutput.bytesPerSample = sizeof(int16)*2;
|
||||
soundOutput.secondaryBufferSize = soundOutput.samplesPerSecond*soundOutput.bytesPerSample;
|
||||
soundOutput.safetyBytes = (soundOutput.samplesPerSecond * soundOutput.bytesPerSample / gameUpdateHz) / 2;
|
||||
soundOutput.safetyBytes = (int)((soundOutput.samplesPerSecond * soundOutput.bytesPerSample / gameUpdateHz) / 2.0f);
|
||||
|
||||
int16 *samples = (int16*)VirtualAlloc(NULL, soundOutput.secondaryBufferSize, MEM_COMMIT, PAGE_READWRITE);
|
||||
|
||||
@@ -623,7 +613,7 @@ int APIENTRY WinMain(HINSTANCE instance, HINSTANCE prevInstance, PSTR commandLin
|
||||
#endif
|
||||
GameMemory gameMemory = {};
|
||||
gameMemory.permanentStorageSize = Megabytes(64);
|
||||
gameMemory.transientStorageSize = Gigabytes((uint64)4);
|
||||
gameMemory.transientStorageSize = Gigabytes((uint64)1);
|
||||
gameMemory.debugFreeFileMemory = debugFreeFileMemory;
|
||||
gameMemory.debugWriteEntireFile = debugWriteEntireFile;
|
||||
gameMemory.debugReadEntireFile = debugReadEntireFile;
|
||||
@@ -634,6 +624,25 @@ int APIENTRY WinMain(HINSTANCE instance, HINSTANCE prevInstance, PSTR commandLin
|
||||
gameMemory.permanentStorage = win32State.gameMemoryBlock;
|
||||
gameMemory.transientStorage = ((uint8 *)gameMemory.permanentStorage + gameMemory.permanentStorageSize);
|
||||
|
||||
for (int i = 0; i < ArrayCount(win32State.replayBuffers); i++) {
|
||||
// Filename
|
||||
char prefix[] = "replay-";
|
||||
char suffix[] = ".hmr";
|
||||
const int totalLen = ArrayCount(prefix) + 1 + ArrayCount(suffix);
|
||||
char replayFilename[totalLen] = {};
|
||||
char digit[] = {(char)(48 + i)};
|
||||
catStrings(ArrayCount(prefix) - 1, prefix, 1, digit, ArrayCount(replayFilename) - 1, replayFilename);
|
||||
catStrings(ArrayCount(prefix), replayFilename, ArrayCount(suffix) - 1, suffix, ArrayCount(replayFilename) - 1, replayFilename);
|
||||
win32GetFullPathToLocalFile(&win32State, replayFilename, sizeof(replayFilename) - 1, win32State.replayBuffers[i].replayFilename);
|
||||
Win32ReplayBuffer *replayBuffer = &win32State.replayBuffers[i];
|
||||
|
||||
replayBuffer->fileHandle = CreateFileA(replayBuffer->replayFilename, GENERIC_WRITE | GENERIC_READ, NULL, NULL, CREATE_ALWAYS, NULL, NULL);
|
||||
DWORD maxSizeLow = (win32State.gameMemoryTotalSize & 0xFFFFFFFF);
|
||||
DWORD maxSizeHigh = (win32State.gameMemoryTotalSize >> 32);
|
||||
replayBuffer->memoryMap = CreateFileMappingA(replayBuffer->fileHandle, NULL, PAGE_READWRITE, maxSizeHigh, maxSizeLow, NULL);
|
||||
replayBuffer->memoryBlock = MapViewOfFile(replayBuffer->memoryMap, FILE_MAP_ALL_ACCESS, NULL, NULL, win32State.gameMemoryTotalSize);
|
||||
}
|
||||
|
||||
win32InitSound(window, soundOutput.samplesPerSecond, soundOutput.secondaryBufferSize);
|
||||
win32ClearBuffer(&soundOutput);
|
||||
globalSecondaryBuffer->Play(0, 0, DSBPLAY_LOOPING);
|
||||
@@ -641,7 +650,6 @@ int APIENTRY WinMain(HINSTANCE instance, HINSTANCE prevInstance, PSTR commandLin
|
||||
LARGE_INTEGER lastWorkCounter = win32GetWallClock();
|
||||
LARGE_INTEGER flipWallClock = win32GetWallClock();
|
||||
|
||||
|
||||
DWORD audioLatencyBytes = 0;
|
||||
real32 audioLatencySeconds = 0;
|
||||
bool soundIsValid = false;
|
||||
@@ -650,6 +658,8 @@ int APIENTRY WinMain(HINSTANCE instance, HINSTANCE prevInstance, PSTR commandLin
|
||||
|
||||
int64 lastCycleCount = __rdtsc();
|
||||
while (globalRunning) {
|
||||
newInput->dtForFrame = targetSecondsPerFrame;
|
||||
|
||||
FILETIME newWriteTime = win32GetLastWriteTime(sourceGameCodeDLLFullPath);
|
||||
if (CompareFileTime(&newWriteTime, &game.lastWriteTime) != 0) {
|
||||
win32UnloadGameCode(&game);
|
||||
@@ -666,6 +676,18 @@ int APIENTRY WinMain(HINSTANCE instance, HINSTANCE prevInstance, PSTR commandLin
|
||||
|
||||
win32ProcessPendingMessages(&win32State, newKeyboardController);
|
||||
|
||||
POINT mousePos;
|
||||
GetCursorPos(&mousePos);
|
||||
ScreenToClient(window, &mousePos);
|
||||
newInput->mouseX = mousePos.x;
|
||||
newInput->mouseY = mousePos.y;
|
||||
newInput->mouseZ = 0; // mouse wheel
|
||||
win32ProcessKeyboardKeypress(&newInput->mouseButtons[0], GetKeyState(VK_LBUTTON) & (1 << 15));
|
||||
win32ProcessKeyboardKeypress(&newInput->mouseButtons[1], GetKeyState(VK_MBUTTON) & (1 << 15));
|
||||
win32ProcessKeyboardKeypress(&newInput->mouseButtons[2], GetKeyState(VK_RBUTTON) & (1 << 15));
|
||||
win32ProcessKeyboardKeypress(&newInput->mouseButtons[3], GetKeyState(VK_XBUTTON1) & (1 << 15));
|
||||
win32ProcessKeyboardKeypress(&newInput->mouseButtons[4], GetKeyState(VK_XBUTTON2) & (1 << 15));
|
||||
|
||||
XINPUT_STATE controllerState;
|
||||
int maxControllerCount = XUSER_MAX_COUNT;
|
||||
if (maxControllerCount > ArrayCount(newInput->controllers) - 1) {
|
||||
@@ -728,6 +750,8 @@ int APIENTRY WinMain(HINSTANCE instance, HINSTANCE prevInstance, PSTR commandLin
|
||||
}
|
||||
}
|
||||
|
||||
ThreadContext threadCtx = {};
|
||||
|
||||
GameOffscreenBuffer videoBuffer = {};
|
||||
videoBuffer.memory = globalBackBuffer.memory;
|
||||
videoBuffer.width = globalBackBuffer.width;
|
||||
@@ -742,20 +766,25 @@ int APIENTRY WinMain(HINSTANCE instance, HINSTANCE prevInstance, PSTR commandLin
|
||||
win32PlaybackInput(&win32State, newInput);
|
||||
}
|
||||
|
||||
game.updateAndRender(&gameMemory, &videoBuffer, newInput);
|
||||
if (game.updateAndRender) {
|
||||
game.updateAndRender(&threadCtx, &gameMemory, &videoBuffer, newInput);
|
||||
}
|
||||
|
||||
LARGE_INTEGER audioWallClock = win32GetWallClock();
|
||||
real32 fromBeginToAudioSeconds = win32GetSecondsElapsed(flipWallClock, audioWallClock);
|
||||
|
||||
DWORD playCursor = 0;
|
||||
DWORD writeCursor = 0;
|
||||
|
||||
if (SUCCEEDED(globalSecondaryBuffer->GetCurrentPosition(&playCursor, &writeCursor))) {
|
||||
if (!soundIsValid) {
|
||||
soundOutput.runningSampleIndex = writeCursor / soundOutput.bytesPerSample;
|
||||
soundIsValid = true;
|
||||
}
|
||||
DWORD byteToLock = (soundOutput.runningSampleIndex*soundOutput.bytesPerSample) % soundOutput.secondaryBufferSize;
|
||||
|
||||
DWORD expectedSoundBytesPerFrame = (soundOutput.samplesPerSecond * soundOutput.bytesPerSample) / gameUpdateHz;
|
||||
DWORD expectedFrameBoundaryByte = playCursor + expectedSoundBytesPerFrame;
|
||||
DWORD expectedSoundBytesPerFrame = (DWORD)((soundOutput.samplesPerSecond * soundOutput.bytesPerSample) / gameUpdateHz);
|
||||
real32 secondsLeftUntilFlip = targetSecondsPerFrame - fromBeginToAudioSeconds;
|
||||
DWORD expectedBytesUntilFlip = (DWORD)((secondsLeftUntilFlip/targetSecondsPerFrame) * (real32)expectedSoundBytesPerFrame);
|
||||
DWORD expectedFrameBoundaryByte = playCursor + expectedBytesUntilFlip;
|
||||
|
||||
DWORD safeWriteCursor = writeCursor;
|
||||
if (safeWriteCursor < playCursor) {
|
||||
@@ -785,25 +814,7 @@ int APIENTRY WinMain(HINSTANCE instance, HINSTANCE prevInstance, PSTR commandLin
|
||||
soundBuffer.samplesPerSecond = soundOutput.samplesPerSecond;
|
||||
soundBuffer.sampleCount = bytesToWrite / soundOutput.bytesPerSample;
|
||||
soundBuffer.samples = samples;
|
||||
game.getSoundSamples(&gameMemory, &soundBuffer);
|
||||
#if HANDMADE_INTERNAL
|
||||
Assert(debugTimeMarkerIndex < ArrayCount(debugMarkers));
|
||||
|
||||
Win32DebugTimeMarker *marker = &debugMarkers[debugTimeMarkerIndex++];
|
||||
|
||||
DWORD unwrappedWriteCursor = writeCursor;
|
||||
if (unwrappedWriteCursor < targetCursor) {
|
||||
unwrappedWriteCursor += soundOutput.secondaryBufferSize;
|
||||
}
|
||||
audioLatencyBytes = unwrappedWriteCursor - playCursor;
|
||||
audioLatencySeconds = (((real32)audioLatencyBytes / (real32)soundOutput.bytesPerSample) / (real32)soundOutput.samplesPerSecond);
|
||||
|
||||
if (debugTimeMarkerIndex >= ArrayCount(debugMarkers)) {
|
||||
debugTimeMarkerIndex = 0;
|
||||
}
|
||||
|
||||
globalSecondaryBuffer->GetCurrentPosition(&marker->playCursor, &marker->writeCursor);
|
||||
#endif
|
||||
game.getSoundSamples(&threadCtx, &gameMemory, &soundBuffer);
|
||||
win32FillSoundBuffer(&soundOutput, byteToLock, bytesToWrite, &soundBuffer);
|
||||
} else {
|
||||
soundIsValid = false;
|
||||
@@ -827,10 +838,6 @@ int APIENTRY WinMain(HINSTANCE instance, HINSTANCE prevInstance, PSTR commandLin
|
||||
|
||||
lastWorkCounter = win32GetWallClock();
|
||||
|
||||
#if HANDMADE_INTERNAL
|
||||
win32DebugSyncDisplay(&globalBackBuffer, ArrayCount(debugMarkers), debugMarkers, &soundOutput, targetSecondsPerFrame);
|
||||
#endif
|
||||
|
||||
win32DrawBufferInWindow(&globalBackBuffer, window);
|
||||
flipWallClock = win32GetWallClock();
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "handmade.h"
|
||||
#include <windows.h>
|
||||
|
||||
struct Win32OffscreenBuffer {
|
||||
BITMAPINFO info;
|
||||
@@ -24,21 +25,39 @@ struct Win32SoundOutput {
|
||||
int safetyBytes;
|
||||
};
|
||||
|
||||
struct Win32DebugTimeMarker {
|
||||
DWORD playCursor;
|
||||
DWORD writeCursor;
|
||||
};
|
||||
|
||||
struct Win32RecordedInput {
|
||||
int inputCount;
|
||||
GameInput *inputStream;
|
||||
};
|
||||
|
||||
#define WIN32_STATE_FILE_NAME_LENGTH MAX_PATH
|
||||
struct Win32ReplayBuffer {
|
||||
HANDLE memoryMap;
|
||||
HANDLE fileHandle;
|
||||
void *memoryBlock;
|
||||
char replayFilename[WIN32_STATE_FILE_NAME_LENGTH];
|
||||
};
|
||||
|
||||
struct Win32State {
|
||||
HANDLE recordingHandle;
|
||||
uint32 inputRecordingIndex;
|
||||
|
||||
HANDLE playbackHandle;
|
||||
uint32 inputPlayingIndex;
|
||||
|
||||
uint64 gameMemoryTotalSize;
|
||||
void *gameMemoryBlock;
|
||||
Win32ReplayBuffer replayBuffers[4];
|
||||
|
||||
char exeFilename[WIN32_STATE_FILE_NAME_LENGTH];
|
||||
char *onePastLastExeFilenameSlash;
|
||||
};
|
||||
|
||||
struct Win32GameCode {
|
||||
HMODULE gameCodeLib;
|
||||
GameUpdateAndRenderFn *updateAndRender;
|
||||
GameGetSoundSamplesFn *getSoundSamples;
|
||||
bool isValid;
|
||||
FILETIME lastWriteTime;
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user