I've applied the patch and run emacs with it. Unfortunately, it doesn't seem to solve the issue. One the bright side, I believe I had buffers which can reliably reproduce the issue (at least locally). I've copied one such file below. The issue can be reproduced by simply going to the end of `main` and attempting to indent the `SQL_Quit()` statement. At first it attempts to overindent the line as `statement-cont`, then if I go up and attempt to indent the `PlatformLog` in the else statement then return to the SDL_Quit and indent it, it has become `topmost-intro`. Apologies for the long length of the file, but hopefully it can help with reproduction:
#include "engine_math.h"
#include "engine_types.h"
#include "platform.h"
#include <SDL.h>
#include <SDL_opengl.h>
#include <sys/mman.h>
#include <x86intrin.h>
const f32 VIEWPORT_WIDTH = 1920;
const f32 VIEWPORT_HEIGHT = 1080;
///////////////////////////////////////////////////////////////////////////////
typedef struct {
game_input Input;
} sdl2_input_state;
typedef struct {
i32 ToneHz;
i32 ToneVolume;
i32 SampleIndex;
i32 SamplesPerSecond;
i32 BytesPerSample;
i32 WavePeriod;
f64 SineLocation;
} sdl2_audio_config;
typedef struct {
u8* Buffer;
i32 Size;
i32 ReadCursor;
i32 WriteCursor;
b32 IsPlaying;
SDL_AudioDeviceID DeviceID;
sdl2_audio_config* AudioConfig;
} sdl2_audio_buffer;
#if HYPERBOREAN_DEBUG
typedef struct {
v2 Pos;
v3 Color;
} sdl2_debug_colored_vertex;
typedef struct {
i32 PlayCursor;
i32 WriteCursor;
sdl2_debug_colored_vertex V[2];
} sdl2_debug_time_marker;
#endif
///////////////////////////////////////////////////////////////////////////////
global_variable b32 GlobalRunning;
global_variable sdl2_input_state GlobalGameInput;
global_variable sdl2_audio_buffer GlobalAudioBuffer;
///////////////////////////////////////////////////////////////////////////////
void PlatformLog(const char* const Format, ...)
{
va_list args;
va_start(args, Format);
SDL_LogMessageV(
SDL_LOG_CATEGORY_APPLICATION,
SDL_LOG_PRIORITY_INFO,
Format,
args
);
va_end(args);
}
b32 PlatformReadEntireFile(const char* path, platform_entire_file* Result) {
FILE* file = fopen(path, "r");
if (file == NULL) {
return false;
}
fseek(file, 0, SEEK_END);
Result->Size = ftell(file);
fseek(file, 0, SEEK_SET);
Result->Contents = (u8*)malloc(Result->Size);
fread(Result->Contents, Result->Size, 1, file);
fclose(file);
return true;
}
void PlatformFreeEntireFile(platform_entire_file* Result) {
if (Result->Contents) {
free(Result->Contents);
Result->Contents = NULL;
Result->Size = 0;
}
}
void PlatformAudioLock() {
SDL_LockAudioDevice(GlobalAudioBuffer.DeviceID);
}
void PlatformAudioUnlock() {
SDL_UnlockAudioDevice(GlobalAudioBuffer.DeviceID);
}
///////////////////////////////////////////////////////////////////////////////
internal void *SDL2VirtualAlloc(u64 SizeBytes, void *BaseAddress = NULL) {
return mmap(BaseAddress, SizeBytes, PROT_READ | PROT_WRITE,
MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
}
///////////////////////////////////////////////////////////////////////////////
internal void SDL2VirtualFree(void *Buffer, u64 SizeBytes) {
munmap(Buffer, SizeBytes);
}
///////////////////////////////////////////////////////////////////////////////
internal bool SDL2VirtualAllocSucceeded(void *Value) {
return Value != MAP_FAILED;
}
///////////////////////////////////////////////////////////////////////////////
internal void SDL2FillAudioDeviceBuffer(void *UserData, u8 *DeviceBuffer,
i32 Length) {
Assert(UserData != NULL);
Assert(DeviceBuffer != NULL);
SDL_memset(DeviceBuffer, 0, Length);
sdl2_audio_buffer *AudioBuffer = (sdl2_audio_buffer *)UserData;
// Keep track of two regions. Region1 contains everything from the current
// ReadCursor up until, potentially, the end of the buffer. Region2 only
// exists if we need to circle back around. It contains all the data from the
// beginning of the buffer up until sufficient bytes are read to meet Length.
int Region1Size = Length;
int Region2Size = 0;
if (AudioBuffer->ReadCursor + Length > AudioBuffer->Size) {
// Handle looping back from the beginning.
Region1Size = AudioBuffer->Size - AudioBuffer->ReadCursor;
Region2Size = Length - Region1Size;
}
SDL_memcpy(DeviceBuffer, (AudioBuffer->Buffer + AudioBuffer->ReadCursor),
Region1Size);
SDL_memcpy(&DeviceBuffer[Region1Size], AudioBuffer->Buffer, Region2Size);
AudioBuffer->ReadCursor =
(AudioBuffer->ReadCursor + Length) % AudioBuffer->Size;
}
///////////////////////////////////////////////////////////////////////////////
internal void SDL2InitializeAudio(sdl2_audio_buffer *AudioBuffer) {
AudioBuffer->Size = AudioBuffer->AudioConfig->SamplesPerSecond *
AudioBuffer->AudioConfig->BytesPerSample;
AudioBuffer->WriteCursor = AudioBuffer->AudioConfig->BytesPerSample;
AudioBuffer->ReadCursor = 0;
AudioBuffer->Buffer = (u8 *)SDL2VirtualAlloc(AudioBuffer->Size);
Assert(SDL2VirtualAllocSucceeded(AudioBuffer->Buffer));
SDL_AudioSpec AudioSettings = {};
AudioSettings.freq = AudioBuffer->AudioConfig->SamplesPerSecond;
AudioSettings.format = AUDIO_S16;
AudioSettings.channels = 2;
// NOTE: Reduce the number of samples here to decrease audio lag by causing
// SDL to request audio more frequently.
AudioSettings.samples = 256;
AudioSettings.callback = &SDL2FillAudioDeviceBuffer;
AudioSettings.userdata = (void *)AudioBuffer;
SDL_AudioSpec ObtainedSettings = {};
AudioBuffer->DeviceID =
SDL_OpenAudioDevice(NULL, 0, &AudioSettings, &ObtainedSettings, 0);
if (AudioSettings.format != ObtainedSettings.format) {
SDL_Log("Could not open audio device: %s", SDL_GetError());
exit(1);
}
}
///////////////////////////////////////////////////////////////////////////////
internal void SDL2Cleanup(sdl2_audio_buffer *AudioBuffer,
sdl2_input_state *InputState) {
if (AudioBuffer->Buffer) {
SDL2VirtualFree(AudioBuffer->Buffer, AudioBuffer->Size);
}
SDL_CloseAudioDevice(AudioBuffer->DeviceID);
}
///////////////////////////////////////////////////////////////////////////////
internal void SDL2ProcessKeyDown(game_button_state *State, b32 IsDown) {
Assert(IsDown != State->EndedDown);
State->EndedDown = IsDown;
++State->HalfTransitionCount;
}
///////////////////////////////////////////////////////////////////////////////
#if HYPERBOREAN_DEBUG
internal u32 SDL2DebugVAO;
internal u32 SDL2DebugVBO;
internal void SDL2DrawSoundBufferMarker(sdl2_audio_buffer *AudioBuffer, f32 C,
f32 PadX, f32 Top, f32 Bottom,
i32 Value,
sdl2_debug_colored_vertex *Vertices,
v3 Color, u32 Shader) {
Assert(Value < AudioBuffer->Size);
f32 XReal = C * Value + PadX;
Vertices[0].Pos = {{XReal, Top}};
Vertices[0].Color = Color;
Vertices[1].Pos = {{XReal, Bottom}};
Vertices[1].Color = Color;
glUseProgram(Shader);
m4x4 Projection =
OrthographicProjection(0, VIEWPORT_WIDTH, 0, VIEWPORT_HEIGHT, -1.0, 1.0);
GLint ProjectionLoc = glGetUniformLocation(Shader, "Projection");
glUniformMatrix4fv(ProjectionLoc, 1, GL_FALSE, (float *)Projection.E);
glBindBuffer(GL_ARRAY_BUFFER, SDL2DebugVBO);
glBufferSubData(GL_ARRAY_BUFFER, 0, 2 * sizeof(sdl2_debug_colored_vertex),
Vertices);
glBindVertexArray(SDL2DebugVAO);
glDrawArrays(GL_LINES, 0, 2);
glBindVertexArray(0);
}
internal void SDL2DebugSyncDisplay(sdl2_audio_buffer *AudioBuffer,
u32 MarkerCount,
sdl2_debug_time_marker *Markers,
f32 TargetSecondsPerFrame, u32 Shader) {
f32 PadX = 16;
f32 PadY = 16;
f32 Top = PadY;
f32 Bottom = VIEWPORT_HEIGHT - PadY;
f32 C = (f32)(VIEWPORT_WIDTH - 2 * PadX) / (f32)AudioBuffer->Size;
for (u32 MarkerIndex = 0; MarkerIndex < MarkerCount; ++MarkerIndex) {
sdl2_debug_time_marker *Marker = &Markers[MarkerIndex];
SDL2DrawSoundBufferMarker(AudioBuffer, C, PadX, Top, Bottom,
Marker->PlayCursor, Marker->V, {{1.0, 1.0, 1.0}},
Shader);
SDL2DrawSoundBufferMarker(AudioBuffer, C, PadX, Top, Bottom,
Marker->WriteCursor, Marker->V, {{1.0, 1.0, 0.0}},
Shader);
}
}
#endif
///////////////////////////////////////////////////////////////////////////////
internal int
SDL2GetWindowRefreshRate(SDL_Window *Window)
{
SDL_DisplayMode Mode;
int DisplayIndex = SDL_GetWindowDisplayIndex(Window);
// If we can't find the refresh rate, we'll return this:
int DefaultRefreshRate = 60;
if (SDL_GetDesktopDisplayMode(DisplayIndex, &Mode) != 0)
{
return DefaultRefreshRate;
}
if (Mode.refresh_rate == 0)
{
return DefaultRefreshRate;
}
return Mode.refresh_rate;
}
///////////////////////////////////////////////////////////////////////////////
internal void SDL2HandleEvent(SDL_Event* Event, sdl2_input_state* InputState)
{
if (Event->type == SDL_QUIT)
{
GlobalRunning = false;
}
else if (Event->type == SDL_KEYDOWN || Event->type == SDL_KEYUP)
{
SDL_Keycode KeyCode = Event->key.keysym.sym;
bool IsDown = Event->key.state == SDL_PRESSED;
bool WasDown = (Event->key.state == SDL_RELEASED || Event->key.repeat != 0);
// Eat key repeats
if (IsDown != WasDown)
{
if (KeyCode == SDLK_LEFT)
{
SDL2ProcessKeyDown(&InputState->Input.Controller.MoveLeft, IsDown);
}
else if (KeyCode == SDLK_RIGHT)
{
SDL2ProcessKeyDown(&InputState->Input.Controller.MoveRight, IsDown);
}
else if (KeyCode == SDLK_UP)
{
SDL2ProcessKeyDown(&InputState->Input.Controller.MoveUp, IsDown);
}
else if (KeyCode == SDLK_DOWN)
{
SDL2ProcessKeyDown(&InputState->Input.Controller.MoveDown, IsDown);
}
else if (KeyCode == SDLK_SPACE)
{
SDL2ProcessKeyDown(&InputState->Input.Controller.ActionDown, IsDown);
}
else if (KeyCode == SDLK_ESCAPE)
{
SDL2ProcessKeyDown(&InputState->Input.Controller.Back, IsDown);
}
}
}
else if (Event->type == SDL_MOUSEMOTION)
{
InputState->Input.MouseP.X = Event->motion.x;
InputState->Input.MouseP.Y = Event->motion.y;
}
else if (Event->type == SDL_MOUSEBUTTONDOWN || Event->type == SDL_MOUSEBUTTONUP)
{
bool IsDown = Event->button.state == SDL_PRESSED;
bool WasDown = (Event->button.state == SDL_RELEASED || Event->button.clicks != 1);
if (IsDown != WasDown) {
if (Event->button.button == SDL_BUTTON_LEFT) {
SDL2ProcessKeyDown(&InputState->Input.MouseLeft, IsDown);
}
if (Event->button.button == SDL_BUTTON_RIGHT) {
SDL2ProcessKeyDown(&InputState->Input.MouseRight, IsDown);
}
}
}
}
///////////////////////////////////////////////////////////////////////////////
int main() {
if (SDL_Init(SDL_INIT_EVERYTHING) == 0)
{
SDL_Window* Window = SDL_CreateWindow(
"Hyperborean",
SDL_WINDOWPOS_UNDEFINED,
SDL_WINDOWPOS_UNDEFINED,
VIEWPORT_WIDTH,
VIEWPORT_HEIGHT,
SDL_WINDOW_OPENGL | SDL_WINDOW_ALLOW_HIGHDPI
);
if (Window != NULL)
{
SDL_GLContext GLContext = SDL_GL_CreateContext(Window);
if (GLContext != NULL)
{
GLenum GlewResult = glewInit();
if (GlewResult == GLEW_OK)
{
PlatformLog("OpenGL Vendor: %s", glGetString(GL_VENDOR));
PlatformLog("OpenGL Version: %s", glGetString(GL_VERSION));
PlatformLog("OpenGL Renderer: %s", glGetString(GL_RENDERER));
if (SDL_GL_SetSwapInterval(1) != 0) {
PlatformLog("Unable to SetSwapInterval: %s", SDL_GetError());
}
game_memory GameMemory = {};
#if HYPERBOREAN_INTERNAL
// NOTE: Hard-code the base address so locations remain constant between runs
void* BaseAddress = (void*)Terabytes(2);
#else
void* BaseAddress = NULL;
#endif
GameMemory.PermanentStorageSize = Megabytes(512);
GameMemory.TransientStorageSize = Gigabytes((u64)4);
u64 TotalSize =
GameMemory.PermanentStorageSize + GameMemory.TransientStorageSize;
GameMemory.PermanentStorage = SDL2VirtualAlloc(TotalSize, BaseAddress);
Assert(SDL2VirtualAllocSucceeded(GameMemory.PermanentStorage));
GameMemory.TransientStorage = (
(u8*)GameMemory.PermanentStorage + GameMemory.PermanentStorageSize);
// Initialize keyboard input
GlobalGameInput = {};
GlobalGameInput.Input.WindowDim.W = VIEWPORT_WIDTH;
GlobalGameInput.Input.WindowDim.H = VIEWPORT_HEIGHT;
GlobalGameInput.Input.Controller.IsConnected = true;
GlobalGameInput.Input.Controller.IsAnalog = true;
u32 MaxVolume = 3000;
GlobalAudioBuffer = {};
sdl2_audio_config AudioConfig = {};
AudioConfig.ToneHz = 261;
AudioConfig.ToneVolume = 0.10 * MaxVolume;
AudioConfig.SampleIndex = 0;
AudioConfig.SamplesPerSecond = 48000;
AudioConfig.BytesPerSample = 2 * sizeof(i16);
AudioConfig.WavePeriod = AudioConfig.SamplesPerSecond / AudioConfig.ToneHz;
GlobalAudioBuffer.AudioConfig = &AudioConfig;
SDL2InitializeAudio(&GlobalAudioBuffer);
// Get screen refresh rate.
PlatformLog("Refresh Rate: %d Hz\n", SDL2GetWindowRefreshRate(Window));
i32 GameUpdateHz = 60;
f32 TargetSecondsPerFrame = 1.0f / (float)GameUpdateHz;
PlatformLog("Target Secs/Frame: %f\n", TargetSecondsPerFrame);
// TODO(eric): Feed DPI information into our game and use it for scaling.
f32 DDPI = 0;
f32 HDPI = 0;
f32 VDPI = 0;
if (SDL_GetDisplayDPI(0, &DDPI, &HDPI, &VDPI) != 0) {
PlatformLog("Unable to get display DPI: %s", SDL_GetError());
}
PlatformLog("Display DPI: %f - %f x %f", DDPI, HDPI, VDPI);
f32 FPS = 0;
f32 MPF = 0;
char WindowTitle[256] = {};
u64 BeginCounter = SDL_GetPerformanceCounter();
u64 PerfCounterFrequency = SDL_GetPerformanceFrequency();
u64 BeginCycles = __rdtsc();
#if HYPERBOREAN_DEBUG
game_state* State = (game_state*)GameMemory.PermanentStorage;
u32 DebugMarkerShader = CompileShaders(
&State->TransientArena,
R"END(
#version 430 core
layout (location = 0) in vec2 Position;
layout (location = 1) in vec3 Color;
uniform mat4 Projection;
out vec3 FragmentColor;
void main()
{
FragmentColor = Color;
gl_Position = vec4(Position, 0.0, 1.0) * Projection;
}
)END",
R"END(
#version 430 core
in vec3 FragmentColor;
out vec4 ResultingColor;
void main()
{
ResultingColor = vec4(FragmentColor, 1.0);
}
)END"
);
u32 DebugTimeMarkerIndex = 0;
sdl2_debug_time_marker DebugTimeMarkers[GameUpdateHz / 2] = {0};
PlatformLog("Num Markers: %d\n", ArrayCount(DebugTimeMarkers));
glGenVertexArrays(1, &SDL2DebugVAO);
glBindVertexArray(SDL2DebugVAO);
glGenBuffers(1, &SDL2DebugVBO);
glBindBuffer(GL_ARRAY_BUFFER, SDL2DebugVBO);
glBufferData(GL_ARRAY_BUFFER, 2*sizeof(sdl2_debug_colored_vertex), NULL, GL_DYNAMIC_DRAW);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(sdl2_debug_colored_vertex), NULL);
glEnableVertexAttribArray(0);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, sizeof(sdl2_debug_colored_vertex), (void*)sizeof(v2));
glEnableVertexAttribArray(1);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
#endif
u32 BeginTimeMs = SDL_GetTicks();
GlobalRunning = true;
SDL_Event event;
while (GlobalRunning) {
while (SDL_PollEvent(&event)) {
SDL2HandleEvent(&event, &GlobalGameInput);
}
game_input GameInput = {};
memcpy(&GameInput, &GlobalGameInput.Input, sizeof(game_input));
game_audio_buffer AudioBuffer = {};
AudioBuffer.Buffer = GlobalAudioBuffer.Buffer;
AudioBuffer.Size = GlobalAudioBuffer.Size;
AudioBuffer.ReadCursor = GlobalAudioBuffer.ReadCursor;
AudioBuffer.WriteCursor = GlobalAudioBuffer.WriteCursor;
AudioBuffer.ToneVolume = GlobalAudioBuffer.AudioConfig->ToneVolume;
AudioBuffer.SamplesPerSecond =
GlobalAudioBuffer.AudioConfig->SamplesPerSecond;
AudioBuffer.BytesPerSample = GlobalAudioBuffer.AudioConfig->BytesPerSample;
SDL_LockAudioDevice(GlobalAudioBuffer.DeviceID);
u32 EndTimeMs = SDL_GetTicks();
GameInput.DeltaTimeSecs = (EndTimeMs - BeginTimeMs) / 1000.0f;
GameUpdateAndRender(&GameMemory, &GameInput, &AudioBuffer);
BeginTimeMs = EndTimeMs;
SDL_UnlockAudioDevice(GlobalAudioBuffer.DeviceID);
// NOTE: Unpause for the first time here to avoid startup lag due
// to SDL already playing audio from an empty buffer. Unpausing
// here allows audio to begin playing immediately at application
// start.
if (!GlobalAudioBuffer.IsPlaying)
{
SDL_PauseAudioDevice(GlobalAudioBuffer.DeviceID, 0);
GlobalAudioBuffer.IsPlaying = true;
}
// NOTE: Copy back out the write cursor state for the circular audio buffer
GlobalAudioBuffer.WriteCursor = AudioBuffer.WriteCursor;
for (size_t ButtonIndex = 0;
ButtonIndex < ArrayCount(GlobalGameInput.Input.Controller.Buttons);
ButtonIndex++)
{
GlobalGameInput.Input.Controller.Buttons[ButtonIndex].HalfTransitionCount = 0;
}
if (GameInput.QuitRequested)
{
GlobalRunning = false;
}
#if HYPERBOREAN_DEBUG
{
sdl2_debug_time_marker* Marker = &DebugTimeMarkers[DebugTimeMarkerIndex++];
if (DebugTimeMarkerIndex >= ArrayCount(DebugTimeMarkers)) {
DebugTimeMarkerIndex = 0;
}
Marker->PlayCursor = GlobalAudioBuffer.ReadCursor;
Marker->WriteCursor = GlobalAudioBuffer.WriteCursor;
SDL2DebugSyncDisplay(&GlobalAudioBuffer, ArrayCount(DebugTimeMarkers), DebugTimeMarkers, TargetSecondsPerFrame, DebugMarkerShader);
}
#endif
SDL_GL_SwapWindow(Window);
u64 EndCounter = SDL_GetPerformanceCounter();
u64 CounterElapsed = EndCounter - BeginCounter;
u64 EndCycles = __rdtsc();
u64 CyclesElapsed = EndCycles - BeginCycles;
FPS = PerfCounterFrequency / (float)CounterElapsed;
f32 SPF = (float)CounterElapsed / PerfCounterFrequency;
MPF = 1000.0f * CounterElapsed / (double)PerfCounterFrequency;
i32 MCPF = SafeTruncateUInt64(CyclesElapsed / (1000 * 1000));
BeginCounter = EndCounter;
BeginCycles = EndCycles;
sprintf(
WindowTitle,
"Hyperborean - %0.02f f/s, %0.04f s/f, %0.02f ms/f, %d mc/f",
FPS, SPF, MPF, MCPF
);
SDL_SetWindowTitle(Window, WindowTitle);
}
GameCleanup(&GameMemory);
#if HYPERBOREAN_DEBUG
glDeleteBuffers(1, &SDL2DebugVAO);
glDeleteBuffers(1, &SDL2DebugVBO);
glDeleteShader(DebugMarkerShader);
#endif
SDL2VirtualFree(GameMemory.PermanentStorage, GameMemory.PermanentStorageSize);
SDL2VirtualFree(GameMemory.TransientStorage, GameMemory.TransientStorageSize);
SDL2Cleanup(&GlobalAudioBuffer, &GlobalGameInput);
}
else
{
PlatformLog("Unable to initialize GLEW: %s\n", glewGetErrorString(GlewResult));
}
SDL_GL_DeleteContext(GLContext);
}
else
{
PlatformLog("Failed to create OpenGL context: %s", SDL_GetError());
}
SDL_DestroyWindow(Window);
}
else
{
PlatformLog("Failed to create window: %s", SDL_GetError());
}
SDL_Quit();
}
else
{
PlatformLog("Failed to initialize SDL: %s", SDL_GetError());
}
return EXIT_SUCCESS;
}