From fc8040d695644aaca4596adebeca4ea1369ef630 Mon Sep 17 00:00:00 2001 From: Fox Caminiti Date: Fri, 22 Jul 2022 20:45:08 -0400 Subject: first --- main.cpp | 596 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 596 insertions(+) create mode 100644 main.cpp (limited to 'main.cpp') diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..ff9dc22 --- /dev/null +++ b/main.cpp @@ -0,0 +1,596 @@ +#include +#include +#include +#if WINDOWS +#else +#include +#endif + +#if ARM +#include +#else +#include +#endif + +#include "imgui/imgui.h" +#include "imgui/backends/imgui_impl_sdl.h" +#include "imgui/backends/imgui_impl_opengl3.h" +#include +#if defined(IMGUI_IMPL_OPENGL_ES2) +#include +#else +#include +#endif + +#define STB_IMAGE_IMPLEMENTATION +#define STBI_FAILURE_USERMSG +#include "lib/stb_image.h" + +extern "C" { +#include +#include +#include +#include +#include +} + +#if 0 +#include +#else +#define IACA_START +#define IACA_END +#endif + +#define internal static +#define local_persist static +#define global_variable static + +#define SwitchBool(Bool) if((Bool)) {(Bool) = 0;} else {(Bool) = 1;} +#define AmountOf(Array) sizeof((Array)) / sizeof((Array)[1]) + +typedef int8_t int8; +typedef int16_t int16; +typedef int32_t int32; + +typedef int64_t int64; +typedef int32 bool32; + +typedef uint8_t uint8; +typedef uint16_t uint16; +typedef uint32_t uint32; +typedef uint64_t uint64; + +typedef float real32; +typedef double real64; + +#define NORMALIZED_COL_MIN { .col = V4(0.0f, 0.0f, 0.0f, 0.0f) } +#define NORMALIZED_COL_MAX { .col = V4(1.0f, 1.0f, 1.0f, 1.0f) } +#define NORMALIZED_REAL_MIN { 0.0f } +#define NORMALIZED_REAL_MAX { 1.0f } + + +// All of these MIN/MAX values are arbitrarily chosen; they can probably be +// increased if the user requires it. + +#define PROPERTY_REAL_MAX 1000000 +#define PROPERTY_REAL_MIN -1000000 + +#define MAX_LAYERS 2048 +#define MAX_EFFECTS 32 +#define MAX_SOURCES 1024 +#define MAX_PROPERTIES_PER_EFFECT 16 +#define MAX_KEYFRAME_BLOCKS 64 +#define MAX_KEYFRAMES_PER_BLOCK 32 +#define STRING_SIZE 256 + +#define MAX_SELECTED_PROPERTIES 16 + +#include "my_math.h" +#include "main.h" +#include "debug.h" + +global_variable uint32 volatile CompletedJobs; +global_variable uint32 volatile NextEntryToDo; +global_variable uint32 volatile EntryCount; +global_variable bool32 IsRendering = false; +global_variable bool32 AVXEnabled = true; + +render_entry Entries[256]; + +SDL_Thread *thread[8]; +SDL_sem *Semaphore; + +#include "memory.cpp" +#include "effects.cpp" +#include "keyframes.cpp" +#include "layer.cpp" +#include "threading.cpp" +#include "prenderer.cpp" +#include "video.cpp" +#include "createcalls.cpp" +#include "my_imgui_widgets.cpp" + +// #include "sharebuffer.h" + +internal void +MainFunction(main_sdl *Main, memory *Memory, + project_state *State, project_data *File, + cache_pool *Cache, pixel_buffer *CompBuffer) +{ + SSE_ClearBuffer(CompBuffer); + for (int i = 0; i < File->NumberOfLayers; i++) { + project_layer *Layer = File->Layer[i]; + if (Layer->RenderInfo) { + // Keyframe updating + if (State->UpdateKeyframes) { + for (int p = 0; p < Layer->NumberOfEffects; p++) { + for (int o = 0; o < Layer->Effect[p]->NumberOfProperties; o++) { + CalculateKeyframesLinearly(File->CurrentFrame, &Layer->Effect[p]->Property[o]); + } + } + for (int r = 0; r < AmountOf(Layer->Property); r++) { + CalculateKeyframesLinearly(File->CurrentFrame, &Layer->Property[r]); + } + State->UpdateKeyframes = false; + } + + // Video updating + if (Layer->SourceType == source_video) { // && Layer->VideoCurrentFrame != File->CurrentFrame - Layer->VideoFrameOffset) { + video_source *Source = (video_source *)Layer->RenderInfo; + LoadVideoFrame(Source, Memory, File->CurrentFrame); // TODO(fox): Make above check work! + UpdateEffects(Layer, Memory); + Source->Raster.ToUpdate = true; + } + + // Effect updating + if (Layer->SourceType == source_image) { + image_source *Source = (image_source *)Layer->RenderInfo; + if (Source->Raster.ToUpdate) { + UpdateEffects(Layer, Memory); + Source->Raster.ToUpdate = false; + } + } + } + } + QueueCurrentFrame(File, CompBuffer, State); +} + +#if 0 +internal void +MainFunction(main_sdl *Main, project_debug *D, memory *Memory, sdl_input *Input, sdl_input *OldInput, + project_state *State, brush_tool *Brush, project_data *File, + cache_pool *Cache, pixel_buffer *CompBuffer) +{ + ClearBuffer(CompBuffer); + + if (Cache->Frame[File->CurrentFrame].Cached) { + FetchCache(&Cache->Frame[File->CurrentFrame], CompBuffer); + } else { + for (int i = 0; i < File->NumberOfLayers; i++) { + if (File->Layer[i]->Raster.OriginalBuffer) { + for (int p = 0; p < File->Layer[i]->NumberOfEffects; p++) { + for (int o = 0; o < File->Layer[i]->Effect[p].NumberOfProperties; o++) { + CalculateKeyframesLinearly(File->CurrentFrame, &File->Layer[i]->Effect[p].Property[o]); + } + } + for (int r = 0; r < AmountOf(File->Layer[i]->Property); r++) { + CalculateKeyframesLinearly(File->CurrentFrame, &File->Layer[i]->Property[r]); + } + if (File->Layer[i]->Raster.ToUpdate) { + UpdateEffects(File->Layer[i], Memory); + File->Layer[i]->Raster.ToUpdate = true; + } + } + } + + if (State->DebugDisableCache) { + RenderCurrentFrame(File, CompBuffer, State); + } else { + if (Cache->Interact == Clear) { + Cache->Intermediate[0].Cached = 0; + Cache->Intermediate[1].Cached = 0; + Cache->Intermediate[2].Cached = 0; + Cache->Interact = Inactive; + } + if (Cache->Interact == Active) { + if (!Cache->Intermediate[0].Address) { + Cache->Intermediate[0].Address = (uint64 *)Memory->Address + Memory->CurrentPosition; + Memory->CurrentPosition += File->Width * File->Height * 4; + Cache->Intermediate[1].Address = (uint64 *)Memory->Address + Memory->CurrentPosition; + Memory->CurrentPosition += File->Width * File->Height * 4; + Cache->Intermediate[2].Address = (uint64 *)Memory->Address + Memory->CurrentPosition; + Memory->CurrentPosition += File->Width * File->Height * 4; + } + pixel_buffer TempBuffer = *CompBuffer; + if (!Cache->Intermediate[0].Cached) { + TempBuffer.OriginalBuffer = Cache->Intermediate[0].Address; + ClearBuffer(&TempBuffer); + for (int i = 0; i < Cache->InteractIndex; i++) { + // RenderLayer(File->LayerPTR[i], &TempBuffer, State); + // RenderCurrentFrame(File->LayerPTR[Cache->InteractIndex], &TempBuffer, State); + } + Cache->Intermediate[0].Cached = 1; + } + TempBuffer.OriginalBuffer = Cache->Intermediate[1].Address; + ClearBuffer(&TempBuffer); + // RenderLayer(File->LayerPTR[Cache->InteractIndex], &TempBuffer, State); + // RenderCurrentFrame(File->LayerPTR[Cache->InteractIndex], &TempBuffer, State); + if (!Cache->Intermediate[2].Cached) { + TempBuffer.OriginalBuffer = Cache->Intermediate[2].Address; + ClearBuffer(&TempBuffer); + for (int i = Cache->InteractIndex + 1; i < File->NumberOfLayers; i++) { + // RenderLayer(File->LayerPTR[i], &TempBuffer, State); + // RenderCurrentFrame(File->LayerPTR[Cache->InteractIndex], &TempBuffer, State); + } + Cache->Intermediate[2].Cached = 1; + } + InteractToComp(CompBuffer, Cache); + } else { + for (int i = 0; i < File->NumberOfLayers; i++) { + } + } + if (!Cache->Interact) { + if (!Cache->Frame[File->CurrentFrame].Address) { + Cache->Frame[File->CurrentFrame].Address = (uint64 *)Memory->Address + Memory->CurrentPosition; + Memory->CurrentPosition += File->Width * File->Height * 4; + } + CacheFrame(&Cache->Frame[File->CurrentFrame], CompBuffer); + Cache->Frame[File->CurrentFrame].Cached = true; + } else { + Cache->Frame[File->CurrentFrame].Cached = false; + } + } + } +} +#endif + + +internal void +DebugPrintMemoryUsage(memory Memory) +{ + for (int i = 0; i < 8; i++) { + memory_table Table = Memory.Slot[i]; + printf("%s: %li bytes, %li kb\n", Table.Name, Table.CurrentPosition, Table.CurrentPosition / 1024); + } +} + + +int main(int argc, char *argv[]) { + + global_memory GlobalMemory = {}; + + GlobalMemory.Size = ((uint64)2 * 1024 * 1024 * 1024); + GlobalMemory.CurrentPosition = 0; + +#if WINDOWS + GlobalMemory.Address = VirtualAlloc(0, GlobalMemory.Size, + MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); +#else + GlobalMemory.Address = mmap(0, GlobalMemory.Size, + PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANONYMOUS, + -1, + 0); +#endif + + memory Memory = {}; + + InitMemoryTable(&GlobalMemory, &Memory, 10 * 1024 * 1024, P_UIState, "UI state"); + // TODO(fox): Make clean-up functions when these get deleted! + InitMemoryTable(&GlobalMemory, &Memory, 10 * 1024 * 1024, P_SourceData, "Image/video headers"); + + InitMemoryTable(&GlobalMemory, &Memory, 10 * 1024 * 1024, F_ProjectSettings, "Project settings"); + InitMemoryTable(&GlobalMemory, &Memory, 10 * 1024 * 1024, F_Layers, "Layers"); + InitMemoryTable(&GlobalMemory, &Memory, 10 * 1024 * 1024, F_Effects, "Effects"); + InitMemoryTable(&GlobalMemory, &Memory, 10 * 1024 * 1024, F_Keyframes, "Keyframe blocks"); + InitMemoryTable(&GlobalMemory, &Memory, 10 * 1024 * 1024, F_Strings, "Strings"); + InitMemoryTable(&GlobalMemory, &Memory, 1024 * 1024 * 1024, B_Scratch, "Scratch buffer"); + + if (!SDL_HasAVX2()) { + AVXEnabled = false; + printf("CPU does not have AVX2!"); + return -1; + } + + + project_state State = {}; + + project_data File = {}; + File.Width = 1280; + File.Height = 720; + File.NumberOfFrames = 65; + File.FPS = 30; + File.CurrentFrame = 1; + File.StartFrame = 0; + File.EndFrame = 65; + + // char String[1024]; + // uint16 Size = 1024; + // getcwd(String, Size); + // printf("dir: %s", String); + + pixel_buffer CompBuffer = CreateBuffer(File.Width, File.Height, &Memory); + + cache_pool Cache = {}; + Cache.Interact = Inactive; + + ui UI = {}; + + File.NumberOfSources = 2; + File.Source[0] = (char *)AllocateMemory(&Memory, STRING_SIZE, F_Strings); + File.Source[1] = (char *)AllocateMemory(&Memory, STRING_SIZE, F_Strings); + sprintf(File.Source[0], "../asset/b.jpg"); + sprintf(File.Source[1], "../asset/24.mp4"); + CreateLayerFromSource(&File, &State, &Memory, File.Source[0]); + CreateLayerFromSource(&File, &State, &Memory, File.Source[1]); + +#if 1 + // shm_unlink("/testl"); + // int fd = shm_open("/testl", O_CREAT | O_EXCL | O_RDWR, + // S_IRUSR | S_IWUSR); + // if (fd == -1) + // errExit("shm_open"); + + // if (ftruncate(fd, SHAREDMEMORY_SIZE) == -1) + // Assert(0); + + // void *asda = mmap(NULL, SHAREDMEMORY_SIZE, + // PROT_READ | PROT_WRITE, + // MAP_SHARED, fd, 0); + // SharedMemoryInfo *shmp = (SharedMemoryInfo *)asda; + + // if (shmp == MAP_FAILED) + // Assert(0); + + // if (sem_init(&shmp->sem1, 1, 0) == -1) + // Assert(0); + // if (sem_init(&shmp->sem2, 1, 0) == -1) + // Assert(0); + + // CreateLayer(&File, &Memory); + // CreateRenderInfo(File.Layer[1], &Memory, File, video, "./asset/24.mp4"); + // File.Layer[1]->Name = "yuyu"; + // File.Layer[1]->StartFrame = 0; + // File.Layer[1]->EndFrame = 65; +#else + CreateDebugLayer(&File, &Memory, 12, 8); + File.Layer[0]->Name = "debug"; + File.Layer[0]->StartFrame = 0; + File.Layer[0]->EndFrame = 65; +#endif + + // CreateLayer(&File, &Memory); + + // CreateRenderInfo(File.Layer[0], &Memory, File, image, "./asset/r.jpg"); + + // File.Layer[0]->Name = "Robot"; + // File.Layer[0]->x.CurrentValue.f = 200; + // File.Layer[0]->y.CurrentValue.f = 250; + // File.Layer[0]->scale.CurrentValue.f = 1.1; + // File.Layer[0]->opacity.CurrentValue.f = 1.0; + // File.Layer[0]->StartFrame = 0; + // File.Layer[0]->EndFrame = 65; + + // ManualKeyframeInsertF(&File.Layer[0]->x, &Memory, 2, 100); + // ManualKeyframeInsertF(&File.Layer[0]->x, &Memory, 60, 500); + + + // ManualKeyframeInsertF(&File.Layer[0]->x, &Memory, 3, 300); + // ManualKeyframeInsertF(&File.Layer[0]->x, &Memory, 8, 800); + // ManualKeyframeInsertF(&File.Layer[0]->x, &Memory, 5, 500); + // ManualKeyframeInsertF(&File.Layer[0]->x, &Memory, 6, 600); + // ManualKeyframeInsertF(&File.Layer[0]->x, &Memory, 7, 700); + + // File.Layer[0]->x.KeyframeBlock[0]->Keyframe[2].IsSelected = true; + // File.Layer[0]->x.KeyframeBlock[0]->Keyframe[3].IsSelected = true; + // File.Layer[0]->x.KeyframeBlock[0]->Keyframe[4].IsSelected = true; + // File.Layer[0]->x.KeyframeBlock[0]->Keyframe[6].IsSelected = true; + + // DeleteSelectedKeyframes(&File, &Memory); + + // for (int16 i = 0; i < 10; i++) { + // ManualKeyframeInsertF(&File.Layer[0]->x, &Memory, i*6 + 3, i*100); + // } + + // int16 kef = 2; + // for (int16 i = 0; i < kef; i++) { + // int16 p = kef - i; + // ManualKeyframeInsertF(&File.Layer[0]->x, &Memory, p*4, p*100); + // } + // &File.Layer[1]->x.KeyframeBlock[0]->Keyframe[2]; + // KeyframeDelete(&File.Layer[1]->x, &Memory, 2); + + // AddEffect(File.Layer[0], &Memory, 0); + // AddEffect(File.Layer[0], &Memory, 0); + + for (int i = 0; i < 3; i++) + // CreateLayer(&File, &Memory); + + // DebugPrintMemoryUsage(Memory); + + SDL_Init(SDL_INIT_VIDEO); + + Semaphore = SDL_CreateSemaphore(0); + + render_queue RenderInfo = {}; + RenderInfo.File = &File; + RenderInfo.State = &State; + RenderInfo.CompBuffer = &CompBuffer; + + thread_info ThreadInfo[7]; + + for (int i = 0; i < 7; i++) { + char str[256]; + ThreadInfo[i].Index = i; + ThreadInfo[i].RenderInfo = &RenderInfo; + thread[i] = SDL_CreateThread(TestThread, str, &ThreadInfo[i]); + } + + sdl_input Input; + sdl_input OldInput; + + // Decide GL+GLSL versions +#if defined(IMGUI_IMPL_OPENGL_ES2) + // GL ES 2.0 + GLSL 100 + const char* glsl_version = "#version 100"; + SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, 0); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0); +#elif defined(__APPLE__) + // GL 3.2 Core + GLSL 150 + const char* glsl_version = "#version 150"; + SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG); // Always required on Mac + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2); +#else + // GL 3.0 + GLSL 130 + const char* glsl_version = "#version 130"; + SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, 0); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0); +#endif + + SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); + SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24); + SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8); + SDL_WindowFlags window_flags = (SDL_WindowFlags)(SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI); + // uint32 ScreenSize[2] = {2560/1.2, 1600/1.2}; + real32 ScreenSize[2] = {3840/1.2, 2160/1.2}; + SDL_Window* window = SDL_CreateWindow("Event Tester", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, ScreenSize[0], ScreenSize[1], window_flags); + SDL_GLContext gl_context = SDL_GL_CreateContext(window); + SDL_GL_MakeCurrent(window, gl_context); + SDL_GL_SetSwapInterval(1); // Enable vsync + + SDL_Event Event; + + IMGUI_CHECKVERSION(); + ImGui::CreateContext(); + ImGuiIO& io = ImGui::GetIO(); + ImGui::GetIO().ConfigFlags |= ImGuiConfigFlags_DockingEnable; + (void)io; + + // NOTE(fox): Instead of constructing the position of the windows on + // startup with Docking API calls (which is experimental and incomplete) + // I'm loading the window positions from this convenient tool. ImGui by + // default saves window position to an external .ini file, which can be + // loaded from disk or memory. + io.IniFilename = NULL; + ImGui::LoadIniSettingsFromMemory(ImGuiPrefs, 1146); + + ImGui::StyleColorsDark(); + + ImGui_ImplSDL2_InitForOpenGL(window, gl_context); + ImGui_ImplOpenGL3_Init(glsl_version); + + GLuint textureID; + glGenTextures(1, &textureID); + glBindTexture(GL_TEXTURE_2D, textureID); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); // This is required on WebGL for non power-of-two textures + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); // Same +#if defined(GL_UNPACK_ROW_LENGTH) && !defined(__EMSCRIPTEN__) + glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); +#endif + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, CompBuffer.Width, CompBuffer.Height, 0, GL_RGBA, GL_UNSIGNED_BYTE, CompBuffer.OriginalBuffer); + + ImVec4 clear_color = ImVec4(0.45f, 0.55f, 0.60f, 1.00f); + + while (State.IsRunning) + { + SDL_Event event; + while (SDL_PollEvent(&event)) + { + ImGui_ImplSDL2_ProcessEvent(&event); + if (event.type == SDL_DROPFILE) { + printf("%s", event.drop.file); + // AddSource(File, Memory, event.drop.file); + } + if (event.type == SDL_QUIT) + State.IsRunning = false; + if (event.type == SDL_WINDOWEVENT && event.window.event == SDL_WINDOWEVENT_CLOSE && event.window.windowID == SDL_GetWindowID(window)) + State.IsRunning = false; + } + + ImGui_ImplOpenGL3_NewFrame(); + ImGui_ImplSDL2_NewFrame(); + + ImGui::NewFrame(); + + if (!io.WantCaptureKeyboard) + ImGui_ProcessInputs(&File, &State, &CompBuffer, &Memory, &UI, io); + + ImGui::DockSpaceOverViewport(); + + ImGui_Viewport(File, &State, &UI, CompBuffer, io, textureID); + + ImGui_File(&File, &State, &Memory, &UI, io); + + ImGui_PropertiesPanel(&File, &State, &UI, &Memory); + + ImGui_Timeline(&File, &State, &Memory, &UI, io); +#if DEBUG + if (Debug.ToggleWindow) + ImGui::ShowDemoWindow(); +#endif + + if (UI.TemporaryUpdateOverride) { + UI.TemporaryUpdateOverride = 0; + State.UpdateFrame = 1; + } + + if (UI.Initializing) + UI.Initializing--; + + // if (File.CurrentFrame != shmp->shared_framenumber) { + // File.CurrentFrame = shmp->shared_framenumber; + // } + + if (State.UpdateFrame && !IsRendering) { + MainFunction(0, &Memory, &State, &File, &Cache, &CompBuffer); + State.UpdateFrame = 0; + } + + if (IsRendering) { + while (CompletedJobs != 16) { + CheckQueue(RenderInfo, 8); + } + if (CompletedJobs == 16) { +#if PACKEDRGB + Unpack4x4Chunk(&CompBuffer); + // SSE_CopyToBuffer(CompBuffer); +#else + PackBitmapRGB(&CompBuffer); +#endif + EndRenderState(&State); + glBindTexture(GL_TEXTURE_2D, textureID); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, CompBuffer.Width, CompBuffer.Height, GL_RGBA, GL_UNSIGNED_BYTE, + CompBuffer.EffectBuffer); + + // shmp->shared_framenumber = File.CurrentFrame; + // if (sem_post(&shmp->sem2) == -1) + // Assert(0); + + } + } + + ImGui::Render(); + glViewport(0, 0, (int)io.DisplaySize.x, (int)io.DisplaySize.y); + glClearColor(clear_color.x * clear_color.w, clear_color.y * clear_color.w, clear_color.z * clear_color.w, clear_color.w); + glClear(GL_COLOR_BUFFER_BIT); + ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); + SDL_GL_SwapWindow(window); + } + for (int i = 0; i < 7; i++) { + SDL_DetachThread(thread[i]); + } + // shm_unlink("/testl"); + ImGui_ImplOpenGL3_Shutdown(); + ImGui_ImplSDL2_Shutdown(); + ImGui::DestroyContext(); + SDL_Quit(); + return 0; +} -- cgit v1.2.3