diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d530b380..770592d9 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -54,6 +54,7 @@ jobs: if not exist ../out/example_1.dll exit 1 if not exist ../out/example_2.dll exit 1 if not exist ../out/example_3.dll exit 1 + if not exist ../out/example_4.dll exit 1 if not exist ../out/pilot_light.exe exit 1 if not exist ../out/pl_graphics_ext.dll exit 1 if not exist ../out/pl_image_ext.dll exit 1 @@ -117,6 +118,7 @@ jobs: test -f ./out/example_1.dylib || exit 1 test -f ./out/example_2.dylib || exit 1 test -f ./out/example_3.dylib || exit 1 + test -f ./out/example_4.dylib || exit 1 test -f ./out/pl_stats_ext.dylib || exit 1 test -f ./out/pl_image_ext.dylib || exit 1 test -f ./out/pl_debug_ext.dylib || exit 1 @@ -192,6 +194,7 @@ jobs: test -f ./out/example_1.so || exit 1 test -f ./out/example_2.so || exit 1 test -f ./out/example_3.so || exit 1 + test -f ./out/example_4.so || exit 1 test -f ./out/pl_graphics_ext.so || exit 1 test -f ./out/pl_image_ext.so || exit 1 test -f ./out/pl_stats_ext.so || exit 1 diff --git a/apps/app.c b/apps/app.c index efd18b6d..fdf4c68b 100644 --- a/apps/app.c +++ b/apps/app.c @@ -5,11 +5,13 @@ /* Index of this file: // [SECTION] includes +// [SECTION] helper functions // [SECTION] structs // [SECTION] pl_app_load // [SECTION] pl_app_shutdown // [SECTION] pl_app_resize // [SECTION] pl_app_update +// [SECTION] helper function implementations */ //----------------------------------------------------------------------------- @@ -25,7 +27,6 @@ Index of this file: #include "pl_memory.h" #define PL_MATH_INCLUDE_FUNCTIONS #include "pl_math.h" -#include "pl_ui.h" // extensions #include "pl_image_ext.h" @@ -37,10 +38,14 @@ Index of this file: #include "pl_model_loader_ext.h" #include "pl_ref_renderer_ext.h" #include "pl_job_ext.h" -#include "pl_draw_3d_ext.h" +#include "pl_draw_ext.h" +#include "pl_ui_ext.h" -// misc -#include "helper_windows.h" +//----------------------------------------------------------------------------- +// [SECTION] helper functions +//----------------------------------------------------------------------------- + +static plEntity pl_show_ecs_window(plComponentLibrary*, bool* pbShowWindow); //----------------------------------------------------------------------------- // [SECTION] structs @@ -85,8 +90,8 @@ typedef struct plAppData_t uint32_t uViewHandle0; // drawing - plDrawLayer* ptDrawLayer; - plFontAtlas tFontAtlas; + plDrawLayer2D* ptDrawLayer; + plFontAtlas tFontAtlas; // sync plSemaphoreHandle atSempahore[PL_FRAMES_IN_FLIGHT]; @@ -115,7 +120,9 @@ const plResourceI* gptResource = NULL; const plRefRendererI* gptRenderer = NULL; const plModelLoaderI* gptModelLoader = NULL; const plJobI* gptJobs = NULL; -const plDraw3dI* gptDraw3d = NULL; +const plDrawI* gptDraw = NULL; +const plUiI* gptUi = NULL; +const plIOI* gptIO = NULL; //----------------------------------------------------------------------------- // [SECTION] pl_app_load @@ -127,7 +134,6 @@ pl_app_load(plApiRegistryI* ptApiRegistry, plAppData* ptAppData) gptApiRegistry = ptApiRegistry; gptDataRegistry = ptApiRegistry->first(PL_API_DATA_REGISTRY); pl_set_memory_context(gptDataRegistry->get_data(PL_CONTEXT_MEMORY)); - pl_set_context(gptDataRegistry->get_data("ui")); if(ptAppData) // reload { @@ -149,7 +155,9 @@ pl_app_load(plApiRegistryI* ptApiRegistry, plAppData* ptAppData) gptRenderer = ptApiRegistry->first(PL_API_REF_RENDERER); gptJobs = ptApiRegistry->first(PL_API_JOB); gptModelLoader = ptApiRegistry->first(PL_API_MODEL_LOADER); - gptDraw3d = ptApiRegistry->first(PL_API_DRAW_3D); + gptDraw = ptApiRegistry->first(PL_API_DRAW); + gptUi = ptApiRegistry->first(PL_API_UI); + gptIO = ptApiRegistry->first(PL_API_IO); return ptAppData; } @@ -180,12 +188,13 @@ pl_app_load(plApiRegistryI* ptApiRegistry, plAppData* ptAppData) ptExtensionRegistry->load("pl_stats_ext", NULL, NULL, false); ptExtensionRegistry->load("pl_graphics_ext", NULL, NULL, false); ptExtensionRegistry->load("pl_gpu_allocators_ext", NULL, NULL, false); - ptExtensionRegistry->load("pl_debug_ext", NULL, NULL, true); ptExtensionRegistry->load("pl_ecs_ext", NULL, NULL, false); ptExtensionRegistry->load("pl_resource_ext", NULL, NULL, false); ptExtensionRegistry->load("pl_model_loader_ext", NULL, NULL, false); - ptExtensionRegistry->load("pl_draw_3d_ext", NULL, NULL, true); + ptExtensionRegistry->load("pl_draw_ext", NULL, NULL, true); ptExtensionRegistry->load("pl_ref_renderer_ext", NULL, NULL, true); + ptExtensionRegistry->load("pl_ui_ext", NULL, NULL, true); + ptExtensionRegistry->load("pl_debug_ext", NULL, NULL, true); // load apis gptWindows = ptApiRegistry->first(PL_API_WINDOW); @@ -202,7 +211,9 @@ pl_app_load(plApiRegistryI* ptApiRegistry, plAppData* ptAppData) gptRenderer = ptApiRegistry->first(PL_API_REF_RENDERER); gptJobs = ptApiRegistry->first(PL_API_JOB); gptModelLoader = ptApiRegistry->first(PL_API_MODEL_LOADER); - gptDraw3d = ptApiRegistry->first(PL_API_DRAW_3D); + gptDraw = ptApiRegistry->first(PL_API_DRAW); + gptUi = ptApiRegistry->first(PL_API_UI); + gptIO = ptApiRegistry->first(PL_API_IO); // initialize job system gptJobs->initialize(0); @@ -216,18 +227,19 @@ pl_app_load(plApiRegistryI* ptApiRegistry, plAppData* ptAppData) }; ptAppData->ptWindow = gptWindows->create_window(&tWindowDesc); - plIO* ptIO = pl_get_io(); + plIO* ptIO = gptIO->get_io(); // setup reference renderer gptRenderer->initialize(ptAppData->ptWindow); - gptDraw3d->initialize(gptRenderer->get_graphics()); + + // setup draw + gptDraw->initialize(gptRenderer->get_graphics()); + gptDraw->add_default_font(&ptAppData->tFontAtlas); + gptDraw->build_font_atlas(&ptAppData->tFontAtlas); // setup ui - pl_add_default_font(&ptAppData->tFontAtlas); - pl_build_font_atlas(&ptAppData->tFontAtlas); - gptGfx->setup_ui(gptRenderer->get_graphics(), gptRenderer->get_graphics()->tMainRenderPass); - gptGfx->create_font_atlas(&ptAppData->tFontAtlas); - pl_set_default_font(&ptAppData->tFontAtlas.sbtFonts[0]); + gptUi->initialize(); + gptUi->set_default_font(&ptAppData->tFontAtlas.sbtFonts[0]); // sync for(uint32_t i = 0; i < PL_FRAMES_IN_FLIGHT; i++) @@ -235,16 +247,16 @@ pl_app_load(plApiRegistryI* ptApiRegistry, plAppData* ptAppData) ptAppData->uSceneHandle0 = gptRenderer->create_scene(); - pl_begin_profile_sample("load environments"); - gptRenderer->load_skybox_from_panorama(ptAppData->uSceneHandle0, "../data/pilotlight-assets-master/environments/helipad.hdr", 1024); - pl_end_profile_sample(); + // pl_begin_profile_sample("load environments"); + // gptRenderer->load_skybox_from_panorama(ptAppData->uSceneHandle0, "../data/pilotlight-assets-master/environments/helipad.hdr", 1024); + // pl_end_profile_sample(); pl_begin_profile_sample("create scene views"); ptAppData->uViewHandle0 = gptRenderer->create_view(ptAppData->uSceneHandle0, (plVec2){ptIO->afMainViewportSize[0] , ptIO->afMainViewportSize[1]}); pl_end_profile_sample(); // temporary draw layer for submitting fullscreen quad of offscreen render - ptAppData->ptDrawLayer = pl_request_layer(pl_get_draw_list(NULL), "draw layer"); + ptAppData->ptDrawLayer = gptDraw->request_2d_layer(gptUi->get_draw_list(), "draw layer"); plComponentLibrary* ptMainComponentLibrary = gptRenderer->get_component_library(ptAppData->uSceneHandle0); @@ -272,7 +284,7 @@ pl_app_load(plApiRegistryI* ptApiRegistry, plAppData* ptAppData) pl_begin_profile_sample("load models 0"); // const plMat4 tTransform0 = pl_mat4_scale_xyz(2.0f, 2.0f, 2.0f); // gptModelLoader->load_gltf(ptMainComponentLibrary, "../data/town.gltf", &tTransform0, &tLoaderData0); - gptModelLoader->load_gltf(ptMainComponentLibrary, "../data/glTF-Sample-Assets-main/Models/Sponza/glTF/Sponza.gltf", NULL, &tLoaderData0); + // gptModelLoader->load_gltf(ptMainComponentLibrary, "../data/glTF-Sample-Assets-main/Models/Sponza/glTF/Sponza.gltf", NULL, &tLoaderData0); gptModelLoader->load_gltf(ptMainComponentLibrary, "../data/glTF-Sample-Assets-main/Models/CesiumMan/glTF/CesiumMan.gltf", NULL, &tLoaderData0); gptRenderer->add_drawable_objects_to_scene(ptAppData->uSceneHandle0, tLoaderData0.uOpaqueCount, tLoaderData0.atOpaqueObjects, tLoaderData0.uTransparentCount, tLoaderData0.atTransparentObjects); gptModelLoader->free_data(&tLoaderData0); @@ -302,9 +314,11 @@ PL_EXPORT void pl_app_shutdown(plAppData* ptAppData) { gptJobs->cleanup(); - gptGfx->destroy_font_atlas(&ptAppData->tFontAtlas); // backend specific cleanup - pl_cleanup_font_atlas(&ptAppData->tFontAtlas); - gptDraw3d->cleanup(); + // ensure GPU is finished before cleanup + gptDevice->flush_device(&gptRenderer->get_graphics()->tDevice); + gptDraw->cleanup_font_atlas(&ptAppData->tFontAtlas); + gptUi->cleanup(); + gptDraw->cleanup(); gptRenderer->cleanup(); gptWindows->destroy_window(ptAppData->ptWindow); pl_cleanup_profile_context(); @@ -320,7 +334,7 @@ PL_EXPORT void pl_app_resize(plAppData* ptAppData) { gptGfx->resize(gptRenderer->get_graphics()); - plIO* ptIO = pl_get_io(); + plIO* ptIO = gptIO->get_io(); gptCamera->set_aspect(gptEcs->get_component(gptRenderer->get_component_library(ptAppData->uSceneHandle0), PL_COMPONENT_TYPE_CAMERA, ptAppData->tMainCamera), ptIO->afMainViewportSize[0] / ptIO->afMainViewportSize[1]); ptAppData->bResize = true; } @@ -336,9 +350,11 @@ pl_app_update(plAppData* ptAppData) pl_begin_profile_frame(); pl_begin_profile_sample(__FUNCTION__); + gptIO->new_frame(); + // for convience plGraphics* ptGraphics = gptRenderer->get_graphics(); - plIO* ptIO = pl_get_io(); + plIO* ptIO = gptIO->get_io(); if(ptAppData->bReloadSwapchain) { @@ -373,7 +389,8 @@ pl_app_update(plAppData* ptAppData) ptAppData->bUpdateEntitySelection = false; } - gptDraw3d->new_frame(); + gptDraw->new_frame(); + gptUi->new_frame(); // update statistics gptStats->new_frame(); @@ -399,22 +416,22 @@ pl_app_update(plAppData* ptAppData) bool bOwnKeyboard = ptIO->bWantCaptureKeyboard; if(!bOwnKeyboard) { - if(pl_is_key_down(PL_KEY_W)) gptCamera->translate(ptCamera, 0.0f, 0.0f, fCameraTravelSpeed * ptIO->fDeltaTime); - if(pl_is_key_down(PL_KEY_S)) gptCamera->translate(ptCamera, 0.0f, 0.0f, -fCameraTravelSpeed* ptIO->fDeltaTime); - if(pl_is_key_down(PL_KEY_A)) gptCamera->translate(ptCamera, -fCameraTravelSpeed * ptIO->fDeltaTime, 0.0f, 0.0f); - if(pl_is_key_down(PL_KEY_D)) gptCamera->translate(ptCamera, fCameraTravelSpeed * ptIO->fDeltaTime, 0.0f, 0.0f); + if(gptIO->is_key_down(PL_KEY_W)) gptCamera->translate(ptCamera, 0.0f, 0.0f, fCameraTravelSpeed * ptIO->fDeltaTime); + if(gptIO->is_key_down(PL_KEY_S)) gptCamera->translate(ptCamera, 0.0f, 0.0f, -fCameraTravelSpeed* ptIO->fDeltaTime); + if(gptIO->is_key_down(PL_KEY_A)) gptCamera->translate(ptCamera, -fCameraTravelSpeed * ptIO->fDeltaTime, 0.0f, 0.0f); + if(gptIO->is_key_down(PL_KEY_D)) gptCamera->translate(ptCamera, fCameraTravelSpeed * ptIO->fDeltaTime, 0.0f, 0.0f); // world space - if(pl_is_key_down(PL_KEY_F)) { gptCamera->translate(ptCamera, 0.0f, -fCameraTravelSpeed * ptIO->fDeltaTime, 0.0f); } - if(pl_is_key_down(PL_KEY_R)) { gptCamera->translate(ptCamera, 0.0f, fCameraTravelSpeed * ptIO->fDeltaTime, 0.0f); } + if(gptIO->is_key_down(PL_KEY_F)) { gptCamera->translate(ptCamera, 0.0f, -fCameraTravelSpeed * ptIO->fDeltaTime, 0.0f); } + if(gptIO->is_key_down(PL_KEY_R)) { gptCamera->translate(ptCamera, 0.0f, fCameraTravelSpeed * ptIO->fDeltaTime, 0.0f); } } bool bOwnMouse = ptIO->bWantCaptureMouse; - if(!bOwnMouse && pl_is_mouse_dragging(PL_MOUSE_BUTTON_LEFT, 1.0f)) + if(!bOwnMouse && gptIO->is_mouse_dragging(PL_MOUSE_BUTTON_LEFT, 1.0f)) { - const plVec2 tMouseDelta = pl_get_mouse_drag_delta(PL_MOUSE_BUTTON_LEFT, 1.0f); + const plVec2 tMouseDelta = gptIO->get_mouse_drag_delta(PL_MOUSE_BUTTON_LEFT, 1.0f); gptCamera->rotate(ptCamera, -tMouseDelta.y * fCameraRotationSpeed, -tMouseDelta.x * fCameraRotationSpeed); - pl_reset_mouse_drag_delta(PL_MOUSE_BUTTON_LEFT); + gptIO->reset_mouse_drag_delta(PL_MOUSE_BUTTON_LEFT); } gptCamera->update(ptCamera); @@ -423,9 +440,6 @@ pl_app_update(plAppData* ptAppData) // run ecs system gptRenderer->run_ecs(ptAppData->uSceneHandle0); - // new ui frame - pl_new_frame(); - uint64_t ulValue0 = ptAppData->aulNextTimelineValue[ptGraphics->uCurrentFrameIndex]; uint64_t ulValue1 = ulValue0 + 1; uint64_t ulValue2 = ulValue0 + 2; @@ -532,83 +546,83 @@ pl_app_update(plAppData* ptAppData) }; tCommandBuffer = gptGfx->begin_command_recording(ptGraphics, &tBeginInfo2); - pl_set_next_window_pos((plVec2){0, 0}, PL_UI_COND_ONCE); + gptUi->set_next_window_pos((plVec2){0, 0}, PL_UI_COND_ONCE); - if(pl_begin_window("Pilot Light", NULL, false)) + if(gptUi->begin_window("Pilot Light", NULL, false)) { const float pfRatios[] = {1.0f}; - pl_layout_row(PL_UI_LAYOUT_ROW_TYPE_DYNAMIC, 0.0f, 1, pfRatios); - if(pl_collapsing_header("Information")) + gptUi->layout_row(PL_UI_LAYOUT_ROW_TYPE_DYNAMIC, 0.0f, 1, pfRatios); + if(gptUi->collapsing_header("Information")) { - pl_text("Pilot Light %s", PILOTLIGHT_VERSION); - pl_text("Pilot Light UI %s", PL_UI_VERSION); - pl_text("Pilot Light DS %s", PL_DS_VERSION); + gptUi->text("Pilot Light %s", PILOTLIGHT_VERSION); + gptUi->text("Pilot Light UI %s", PL_UI_EXT_VERSION); + gptUi->text("Pilot Light DS %s", PL_DS_VERSION); #ifdef PL_METAL_BACKEND - pl_text("Graphics Backend: Metal"); + gptUi->text("Graphics Backend: Metal"); #elif PL_VULKAN_BACKEND - pl_text("Graphics Backend: Vulkan"); + gptUi->text("Graphics Backend: Vulkan"); #else - pl_text("Graphics Backend: Unknown"); + gptUi->text("Graphics Backend: Unknown"); #endif - pl_end_collapsing_header(); + gptUi->end_collapsing_header(); } - if(pl_collapsing_header("General Options")) + if(gptUi->collapsing_header("General Options")) { - if(pl_checkbox("VSync", &ptGraphics->tSwapchain.bVSync)) + if(gptUi->checkbox("VSync", &ptGraphics->tSwapchain.bVSync)) ptAppData->bReloadSwapchain = true; - pl_checkbox("Frustum Culling", &ptAppData->bFrustumCulling); - if(pl_checkbox("Freeze Culling Camera", &ptAppData->bFreezeCullCamera)) + gptUi->checkbox("Frustum Culling", &ptAppData->bFrustumCulling); + if(gptUi->checkbox("Freeze Culling Camera", &ptAppData->bFreezeCullCamera)) { *ptCullCamera = *ptCamera; } - pl_checkbox("Draw All Bounding Boxes", &ptAppData->bDrawAllBoundingBoxes); - pl_checkbox("Draw Visible Bounding Boxes", &ptAppData->bDrawVisibleBoundingBoxes); - pl_end_collapsing_header(); + gptUi->checkbox("Draw All Bounding Boxes", &ptAppData->bDrawAllBoundingBoxes); + gptUi->checkbox("Draw Visible Bounding Boxes", &ptAppData->bDrawVisibleBoundingBoxes); + gptUi->end_collapsing_header(); } - if(pl_collapsing_header("Tools")) + if(gptUi->collapsing_header("Tools")) { - pl_checkbox("Device Memory Analyzer", &ptAppData->tDebugInfo.bShowDeviceMemoryAnalyzer); - pl_checkbox("Memory Allocations", &ptAppData->tDebugInfo.bShowMemoryAllocations); - pl_checkbox("Profiling", &ptAppData->tDebugInfo.bShowProfiling); - pl_checkbox("Statistics", &ptAppData->tDebugInfo.bShowStats); - pl_checkbox("Logging", &ptAppData->tDebugInfo.bShowLogging); - pl_checkbox("Entities", &ptAppData->bShowEntityWindow); - pl_end_collapsing_header(); + gptUi->checkbox("Device Memory Analyzer", &ptAppData->tDebugInfo.bShowDeviceMemoryAnalyzer); + gptUi->checkbox("Memory Allocations", &ptAppData->tDebugInfo.bShowMemoryAllocations); + gptUi->checkbox("Profiling", &ptAppData->tDebugInfo.bShowProfiling); + gptUi->checkbox("Statistics", &ptAppData->tDebugInfo.bShowStats); + gptUi->checkbox("Logging", &ptAppData->tDebugInfo.bShowLogging); + gptUi->checkbox("Entities", &ptAppData->bShowEntityWindow); + gptUi->end_collapsing_header(); } - if(pl_collapsing_header("Debug")) + if(gptUi->collapsing_header("Debug")) { - if(pl_button("resize")) + if(gptUi->button("resize")) ptAppData->bResize = true; - pl_checkbox("Always Resize", &ptAppData->bAlwaysResize); + gptUi->checkbox("Always Resize", &ptAppData->bAlwaysResize); plLightComponent* ptLight = gptEcs->get_component(ptMainComponentLibrary, PL_COMPONENT_TYPE_LIGHT, ptAppData->tSunlight); - pl_slider_float("split", &ptAppData->fCascadeSplitLambda, 0.0f, 1.0f); - pl_slider_float("x", &ptLight->tDirection.x, -1.0f, 1.0f); - pl_slider_float("y", &ptLight->tDirection.y, -1.0f, 1.0f); - pl_slider_float("z", &ptLight->tDirection.z, -1.0f, 1.0f); + gptUi->slider_float("split", &ptAppData->fCascadeSplitLambda, 0.0f, 1.0f); + gptUi->slider_float("x", &ptLight->tDirection.x, -1.0f, 1.0f); + gptUi->slider_float("y", &ptLight->tDirection.y, -1.0f, 1.0f); + gptUi->slider_float("z", &ptLight->tDirection.z, -1.0f, 1.0f); - pl_end_collapsing_header(); + gptUi->end_collapsing_header(); } - if(pl_collapsing_header("User Interface")) + if(gptUi->collapsing_header("User Interface")) { - pl_checkbox("UI Debug", &ptAppData->bShowUiDebug); - pl_checkbox("UI Demo", &ptAppData->bShowUiDemo); - pl_checkbox("UI Style", &ptAppData->bShowUiStyle); - pl_end_collapsing_header(); + gptUi->checkbox("UI Debug", &ptAppData->bShowUiDebug); + gptUi->checkbox("UI Demo", &ptAppData->bShowUiDemo); + gptUi->checkbox("UI Style", &ptAppData->bShowUiStyle); + gptUi->end_collapsing_header(); } - pl_end_window(); + gptUi->end_window(); } gptDebug->show_debug_windows(&ptAppData->tDebugInfo); if(ptAppData->bShowEntityWindow) { - plEntity tNextSelectedEntity = pl_show_ecs_window(gptEcs, gptRenderer->get_component_library(ptAppData->uSceneHandle0), &ptAppData->bShowEntityWindow); + plEntity tNextSelectedEntity = pl_show_ecs_window(gptRenderer->get_component_library(ptAppData->uSceneHandle0), &ptAppData->bShowEntityWindow); if(tNextSelectedEntity.ulData != ptAppData->tSelectedEntity.ulData) ptAppData->bUpdateEntitySelection = true; ptAppData->tSelectedEntity = tNextSelectedEntity; @@ -617,27 +631,25 @@ pl_app_update(plAppData* ptAppData) if(ptAppData->bShowUiDemo) { pl_begin_profile_sample("ui demo"); - pl_show_demo_window(&ptAppData->bShowUiDemo); + gptUi->show_demo_window(&ptAppData->bShowUiDemo); pl_end_profile_sample(); } if(ptAppData->bShowUiStyle) - pl_show_style_editor_window(&ptAppData->bShowUiStyle); + gptUi->show_style_editor_window(&ptAppData->bShowUiStyle); if(ptAppData->bShowUiDebug) - pl_show_debug_window(&ptAppData->bShowUiDebug); + gptUi->show_debug_window(&ptAppData->bShowUiDebug); // add full screen quad for offscreen render - pl_add_image(ptAppData->ptDrawLayer, gptRenderer->get_view_texture_id(ptAppData->uSceneHandle0, ptAppData->uViewHandle0), (plVec2){0}, (plVec2){ptIO->afMainViewportSize[0], ptIO->afMainViewportSize[1]}); - pl_submit_layer(ptAppData->ptDrawLayer); + gptDraw->add_image(ptAppData->ptDrawLayer, gptRenderer->get_view_color_texture(ptAppData->uSceneHandle0, ptAppData->uViewHandle0), (plVec2){0}, (plVec2){ptIO->afMainViewportSize[0], ptIO->afMainViewportSize[1]}); + gptDraw->submit_2d_layer(ptAppData->ptDrawLayer); plRenderEncoder tEncoder = gptGfx->begin_render_pass(ptGraphics, &tCommandBuffer, ptGraphics->tMainRenderPass); // render ui pl_begin_profile_sample("render ui"); - pl_render(); - gptGfx->draw_lists(ptGraphics, tEncoder, 1, pl_get_draw_list(NULL)); - gptGfx->draw_lists(ptGraphics, tEncoder, 1, pl_get_debug_draw_list(NULL)); + gptUi->render(tEncoder, ptIO->afMainViewportSize[0], ptIO->afMainViewportSize[1], 1); pl_end_profile_sample(); gptGfx->end_render_pass(&tEncoder); @@ -653,4 +665,298 @@ pl_app_update(plAppData* ptAppData) pl_end_profile_sample(); pl_end_profile_frame(); +} + +//----------------------------------------------------------------------------- +// [SECTION] helper function implementations +//----------------------------------------------------------------------------- + +static plEntity +pl_show_ecs_window(plComponentLibrary* ptLibrary, bool* pbShowWindow) +{ + static int iSelectedEntity = -1; + static plEntity tSelectedEntity = {UINT32_MAX, UINT32_MAX}; + if(gptUi->begin_window("Entities", pbShowWindow, false)) + { + const plVec2 tWindowSize = gptUi->get_window_size(); + const float pfRatios[] = {0.5f, 0.5f}; + gptUi->layout_row(PL_UI_LAYOUT_ROW_TYPE_DYNAMIC, 0.0f, 2, pfRatios); + gptUi->text("Entities"); + gptUi->text("Components"); + gptUi->layout_dynamic(0.0f, 1); + gptUi->separator(); + gptUi->layout_row(PL_UI_LAYOUT_ROW_TYPE_DYNAMIC, tWindowSize.y - 75.0f, 2, pfRatios); + + + if(gptUi->begin_child("Entities")) + { + const float pfRatiosInner[] = {1.0f}; + gptUi->layout_row(PL_UI_LAYOUT_ROW_TYPE_DYNAMIC, 0.0f, 1, pfRatiosInner); + + const uint32_t uEntityCount = pl_sb_size(ptLibrary->tTagComponentManager.sbtEntities); + plTagComponent* sbtTags = ptLibrary->tTagComponentManager.pComponents; + + plUiClipper tClipper = {(uint32_t)uEntityCount}; + while(gptUi->step_clipper(&tClipper)) + { + for(uint32_t i = tClipper.uDisplayStart; i < tClipper.uDisplayEnd; i++) + { + bool bSelected = (int)i == iSelectedEntity; + char atBuffer[1024] = {0}; + pl_sprintf(atBuffer, "%s ##%u", sbtTags[i].acName, i); + if(gptUi->selectable(atBuffer, &bSelected)) + { + if(bSelected) + { + iSelectedEntity = (int)i; + tSelectedEntity = ptLibrary->tTagComponentManager.sbtEntities[i]; + } + else + { + iSelectedEntity = -1; + tSelectedEntity.uIndex = UINT32_MAX; + tSelectedEntity.uGeneration = UINT32_MAX; + } + } + } + } + + gptUi->end_child(); + } + + if(gptUi->begin_child("Components")) + { + const float pfRatiosInner[] = {1.0f}; + gptUi->layout_row(PL_UI_LAYOUT_ROW_TYPE_DYNAMIC, 0.0f, 1, pfRatiosInner); + + if(iSelectedEntity != -1) + { + plTagComponent* ptTagComp = gptEcs->get_component(ptLibrary, PL_COMPONENT_TYPE_TAG, tSelectedEntity); + plTransformComponent* ptTransformComp = gptEcs->get_component(ptLibrary, PL_COMPONENT_TYPE_TRANSFORM, tSelectedEntity); + plMeshComponent* ptMeshComp = gptEcs->get_component(ptLibrary, PL_COMPONENT_TYPE_MESH, tSelectedEntity); + plObjectComponent* ptObjectComp = gptEcs->get_component(ptLibrary, PL_COMPONENT_TYPE_OBJECT, tSelectedEntity); + plHierarchyComponent* ptHierarchyComp = gptEcs->get_component(ptLibrary, PL_COMPONENT_TYPE_HIERARCHY, tSelectedEntity); + plMaterialComponent* ptMaterialComp = gptEcs->get_component(ptLibrary, PL_COMPONENT_TYPE_MATERIAL, tSelectedEntity); + plSkinComponent* ptSkinComp = gptEcs->get_component(ptLibrary, PL_COMPONENT_TYPE_SKIN, tSelectedEntity); + plCameraComponent* ptCameraComp = gptEcs->get_component(ptLibrary, PL_COMPONENT_TYPE_CAMERA, tSelectedEntity); + plAnimationComponent* ptAnimationComp = gptEcs->get_component(ptLibrary, PL_COMPONENT_TYPE_ANIMATION, tSelectedEntity); + plInverseKinematicsComponent* ptIKComp = gptEcs->get_component(ptLibrary, PL_COMPONENT_TYPE_INVERSE_KINEMATICS, tSelectedEntity); + plLightComponent* ptLightComp = gptEcs->get_component(ptLibrary, PL_COMPONENT_TYPE_LIGHT, tSelectedEntity); + + gptUi->text("Entity: %u, %u", tSelectedEntity.uIndex, tSelectedEntity.uGeneration); + + if(ptTagComp && gptUi->collapsing_header("Tag")) + { + gptUi->text("Name: %s", ptTagComp->acName); + gptUi->end_collapsing_header(); + } + + if(ptTransformComp && gptUi->collapsing_header("Transform")) + { + gptUi->text("Scale: (%+0.3f, %+0.3f, %+0.3f)", ptTransformComp->tScale.x, ptTransformComp->tScale.y, ptTransformComp->tScale.z); + gptUi->text("Translation: (%+0.3f, %+0.3f, %+0.3f)", ptTransformComp->tTranslation.x, ptTransformComp->tTranslation.y, ptTransformComp->tTranslation.z); + gptUi->text("Rotation: (%+0.3f, %+0.3f, %+0.3f, %+0.3f)", ptTransformComp->tRotation.x, ptTransformComp->tRotation.y, ptTransformComp->tRotation.z, ptTransformComp->tRotation.w); + gptUi->vertical_spacing(); + gptUi->text("Local World: |%+0.3f, %+0.3f, %+0.3f, %+0.3f|", ptTransformComp->tWorld.col[0].x, ptTransformComp->tWorld.col[1].x, ptTransformComp->tWorld.col[2].x, ptTransformComp->tWorld.col[3].x); + gptUi->text(" |%+0.3f, %+0.3f, %+0.3f, %+0.3f|", ptTransformComp->tWorld.col[0].y, ptTransformComp->tWorld.col[1].y, ptTransformComp->tWorld.col[2].y, ptTransformComp->tWorld.col[3].y); + gptUi->text(" |%+0.3f, %+0.3f, %+0.3f, %+0.3f|", ptTransformComp->tWorld.col[0].z, ptTransformComp->tWorld.col[1].z, ptTransformComp->tWorld.col[2].z, ptTransformComp->tWorld.col[3].z); + gptUi->text(" |%+0.3f, %+0.3f, %+0.3f, %+0.3f|", ptTransformComp->tWorld.col[0].w, ptTransformComp->tWorld.col[1].w, ptTransformComp->tWorld.col[2].w, ptTransformComp->tWorld.col[3].w); + gptUi->end_collapsing_header(); + } + + if(ptMeshComp && gptUi->collapsing_header("Mesh")) + { + + plTagComponent* ptMaterialTagComp = gptEcs->get_component(ptLibrary, PL_COMPONENT_TYPE_TAG, ptMeshComp->tMaterial); + plTagComponent* ptSkinTagComp = gptEcs->get_component(ptLibrary, PL_COMPONENT_TYPE_TAG, ptMeshComp->tSkinComponent); + gptUi->text("Material: %s", ptMaterialTagComp->acName); + gptUi->text("Skin: %s", ptSkinTagComp ? ptSkinTagComp->acName : " "); + + gptUi->vertical_spacing(); + gptUi->text("Vertex Data (%u verts, %u idx)", pl_sb_size(ptMeshComp->sbtVertexPositions), pl_sb_size(ptMeshComp->sbuIndices)); + gptUi->indent(15.0f); + gptUi->text("%s Positions", ptMeshComp->ulVertexStreamMask & PL_MESH_FORMAT_FLAG_HAS_POSITION ? "ACTIVE" : " "); + gptUi->text("%s Normals", ptMeshComp->ulVertexStreamMask & PL_MESH_FORMAT_FLAG_HAS_NORMAL ? "ACTIVE" : " "); + gptUi->text("%s Tangents", ptMeshComp->ulVertexStreamMask & PL_MESH_FORMAT_FLAG_HAS_TANGENT ? "ACTIVE" : " "); + gptUi->text("%s Texture Coordinates 0", ptMeshComp->ulVertexStreamMask & PL_MESH_FORMAT_FLAG_HAS_TEXCOORD_0 ? "ACTIVE" : " "); + gptUi->text("%s Texture Coordinates 1", ptMeshComp->ulVertexStreamMask & PL_MESH_FORMAT_FLAG_HAS_TEXCOORD_1 ? "ACTIVE" : " "); + gptUi->text("%s Colors 0", ptMeshComp->ulVertexStreamMask & PL_MESH_FORMAT_FLAG_HAS_COLOR_0 ? "ACTIVE" : " "); + gptUi->text("%s Colors 1", ptMeshComp->ulVertexStreamMask & PL_MESH_FORMAT_FLAG_HAS_COLOR_1 ? "ACTIVE" : " "); + gptUi->text("%s Joints 0", ptMeshComp->ulVertexStreamMask & PL_MESH_FORMAT_FLAG_HAS_JOINTS_0 ? "ACTIVE" : " "); + gptUi->text("%s Joints 1", ptMeshComp->ulVertexStreamMask & PL_MESH_FORMAT_FLAG_HAS_JOINTS_1 ? "ACTIVE" : " "); + gptUi->text("%s Weights 0", ptMeshComp->ulVertexStreamMask & PL_MESH_FORMAT_FLAG_HAS_WEIGHTS_0 ? "ACTIVE" : " "); + gptUi->text("%s Weights 1", ptMeshComp->ulVertexStreamMask & PL_MESH_FORMAT_FLAG_HAS_WEIGHTS_1 ? "ACTIVE" : " "); + gptUi->unindent(15.0f); + gptUi->end_collapsing_header(); + } + + if(ptObjectComp && gptUi->collapsing_header("Object")) + { + plTagComponent* ptMeshTagComp = gptEcs->get_component(ptLibrary, PL_COMPONENT_TYPE_TAG, ptObjectComp->tMesh); + plTagComponent* ptTransformTagComp = gptEcs->get_component(ptLibrary, PL_COMPONENT_TYPE_TAG, ptObjectComp->tTransform); + gptUi->text("Mesh Entity: %s", ptMeshTagComp->acName); + gptUi->text("Transform Entity: %s, %u", ptTransformTagComp->acName, ptObjectComp->tTransform.uIndex); + gptUi->end_collapsing_header(); + } + + if(ptHierarchyComp && gptUi->collapsing_header("Hierarchy")) + { + plTagComponent* ptParentTagComp = gptEcs->get_component(ptLibrary, PL_COMPONENT_TYPE_TAG, ptHierarchyComp->tParent); + gptUi->text("Parent Entity: %s , %u", ptParentTagComp->acName, ptHierarchyComp->tParent.uIndex); + gptUi->end_collapsing_header(); + } + + if(ptLightComp && gptUi->collapsing_header("Light")) + { + static const char* apcLightTypes[] = { + "PL_LIGHT_TYPE_DIRECTIONAL", + "PL_LIGHT_TYPE_POINT" + }; + gptUi->text("Type: %s", apcLightTypes[ptLightComp->tType]); + gptUi->text("Position: (%0.3f, %0.3f, %0.3f)", ptLightComp->tPosition.r, ptLightComp->tPosition.g, ptLightComp->tPosition.b); + gptUi->text("Color: (%0.3f, %0.3f, %0.3f)", ptLightComp->tColor.r, ptLightComp->tColor.g, ptLightComp->tColor.b); + gptUi->text("Direction: (%0.3f, %0.3f, %0.3f)", ptLightComp->tDirection.r, ptLightComp->tDirection.g, ptLightComp->tDirection.b); + gptUi->text("Intensity: %0.3f", ptLightComp->fIntensity); + gptUi->text("Cast Shadow: %s", ptLightComp->tFlags & PL_LIGHT_FLAG_CAST_SHADOW ? "true" : "false"); + } + + if(ptMaterialComp && gptUi->collapsing_header("Material")) + { + gptUi->text("Base Color: (%0.3f, %0.3f, %0.3f, %0.3f)", ptMaterialComp->tBaseColor.r, ptMaterialComp->tBaseColor.g, ptMaterialComp->tBaseColor.b, ptMaterialComp->tBaseColor.a); + gptUi->text("Alpha Cutoff: %0.3f", ptMaterialComp->fAlphaCutoff); + + static const char* apcBlendModeNames[] = + { + "PL_MATERIAL_BLEND_MODE_OPAQUE", + "PL_MATERIAL_BLEND_MODE_ALPHA", + "PL_MATERIAL_BLEND_MODE_PREMULTIPLIED", + "PL_MATERIAL_BLEND_MODE_ADDITIVE", + "PL_MATERIAL_BLEND_MODE_MULTIPLY" + }; + gptUi->text("Blend Mode: %s", apcBlendModeNames[ptMaterialComp->tBlendMode]); + + static const char* apcShaderNames[] = + { + "PL_SHADER_TYPE_PBR", + "PL_SHADER_TYPE_UNLIT", + "PL_SHADER_TYPE_CUSTOM" + }; + gptUi->text("Shader Type: %s", apcShaderNames[ptMaterialComp->tShaderType]); + gptUi->text("Double Sided: %s", ptMaterialComp->tFlags & PL_MATERIAL_FLAG_DOUBLE_SIDED ? "true" : "false"); + + gptUi->vertical_spacing(); + gptUi->text("Texture Maps"); + gptUi->indent(15.0f); + + static const char* apcTextureSlotNames[] = + { + "PL_TEXTURE_SLOT_BASE_COLOR_MAP", + "PL_TEXTURE_SLOT_NORMAL_MAP", + "PL_TEXTURE_SLOT_EMISSIVE_MAP", + "PL_TEXTURE_SLOT_OCCLUSION_MAP", + "PL_TEXTURE_SLOT_METAL_ROUGHNESS_MAP", + "PL_TEXTURE_SLOT_CLEARCOAT_MAP", + "PL_TEXTURE_SLOT_CLEARCOAT_ROUGHNESS_MAP", + "PL_TEXTURE_SLOT_CLEARCOAT_NORMAL_MAP", + "PL_TEXTURE_SLOT_SHEEN_COLOR_MAP", + "PL_TEXTURE_SLOT_SHEEN_ROUGHNESS_MAP", + "PL_TEXTURE_SLOT_TRANSMISSION_MAP", + "PL_TEXTURE_SLOT_SPECULAR_MAP", + "PL_TEXTURE_SLOT_SPECULAR_COLOR_MAP", + "PL_TEXTURE_SLOT_ANISOTROPY_MAP", + "PL_TEXTURE_SLOT_SURFACE_MAP", + "PL_TEXTURE_SLOT_IRIDESCENCE_MAP", + "PL_TEXTURE_SLOT_IRIDESCENCE_THICKNESS_MAP" + }; + + for(uint32_t i = 0; i < PL_TEXTURE_SLOT_COUNT; i++) + { + gptUi->text("%s: %s", apcTextureSlotNames[i], ptMaterialComp->atTextureMaps[i].acName[0] == 0 ? " " : "present"); + } + gptUi->unindent(15.0f); + gptUi->end_collapsing_header(); + } + + if(ptSkinComp && gptUi->collapsing_header("Skin")) + { + if(gptUi->tree_node("Joints")) + { + for(uint32_t i = 0; i < pl_sb_size(ptSkinComp->sbtJoints); i++) + { + plTagComponent* ptJointTagComp = gptEcs->get_component(ptLibrary, PL_COMPONENT_TYPE_TAG, ptSkinComp->sbtJoints[i]); + gptUi->text("%s", ptJointTagComp->acName); + } + gptUi->tree_pop(); + } + gptUi->end_collapsing_header(); + } + + if(ptCameraComp && gptUi->collapsing_header("Camera")) + { + gptUi->text("Near Z: %+0.3f", ptCameraComp->fNearZ); + gptUi->text("Far Z: %+0.3f", ptCameraComp->fFarZ); + gptUi->text("Vertical Field of View: %+0.3f", ptCameraComp->fFieldOfView); + gptUi->text("Aspect Ratio: %+0.3f", ptCameraComp->fAspectRatio); + gptUi->text("Pitch: %+0.3f", ptCameraComp->fPitch); + gptUi->text("Yaw: %+0.3f", ptCameraComp->fYaw); + gptUi->text("Roll: %+0.3f", ptCameraComp->fRoll); + gptUi->text("Position: (%+0.3f, %+0.3f, %+0.3f)", ptCameraComp->tPos.x, ptCameraComp->tPos.y, ptCameraComp->tPos.z); + gptUi->text("Up: (%+0.3f, %+0.3f, %+0.3f)", ptCameraComp->_tUpVec.x, ptCameraComp->_tUpVec.y, ptCameraComp->_tUpVec.z); + gptUi->text("Forward: (%+0.3f, %+0.3f, %+0.3f)", ptCameraComp->_tForwardVec.x, ptCameraComp->_tForwardVec.y, ptCameraComp->_tForwardVec.z); + gptUi->text("Right: (%+0.3f, %+0.3f, %+0.3f)", ptCameraComp->_tRightVec.x, ptCameraComp->_tRightVec.y, ptCameraComp->_tRightVec.z); + gptUi->slider_float("Far Z Plane", &ptCameraComp->fFarZ, 10.0f, 400.0f); + gptUi->end_collapsing_header(); + } + + if(ptAnimationComp && gptUi->collapsing_header("Animation")) + { + bool bPlaying = ptAnimationComp->tFlags & PL_ANIMATION_FLAG_PLAYING; + bool bLooped = ptAnimationComp->tFlags & PL_ANIMATION_FLAG_LOOPED; + if(bLooped && bPlaying) + gptUi->text("Status: playing & looped"); + else if(bPlaying) + gptUi->text("Status: playing"); + else if(bLooped) + gptUi->text("Status: looped"); + else + gptUi->text("Status: not playing"); + if(gptUi->checkbox("Playing", &bPlaying)) + { + if(bPlaying) + ptAnimationComp->tFlags |= PL_ANIMATION_FLAG_PLAYING; + else + ptAnimationComp->tFlags &= ~PL_ANIMATION_FLAG_PLAYING; + } + if(gptUi->checkbox("Looped", &bLooped)) + { + if(bLooped) + ptAnimationComp->tFlags |= PL_ANIMATION_FLAG_LOOPED; + else + ptAnimationComp->tFlags &= ~PL_ANIMATION_FLAG_LOOPED; + } + gptUi->text("Start: %0.3f s", ptAnimationComp->fStart); + gptUi->text("End: %0.3f s", ptAnimationComp->fEnd); + gptUi->progress_bar(ptAnimationComp->fTimer / (ptAnimationComp->fEnd - ptAnimationComp->fStart), (plVec2){-1.0f, 0.0f}, NULL); + gptUi->text("Speed: %0.3f s", ptAnimationComp->fSpeed); + gptUi->end_collapsing_header(); + } + + if(ptIKComp && gptUi->collapsing_header("Inverse Kinematics")) + { + plTagComponent* ptTargetComp = gptEcs->get_component(ptLibrary, PL_COMPONENT_TYPE_TAG, ptIKComp->tTarget); + gptUi->text("Target Entity: %s , %u", ptTargetComp->acName, ptIKComp->tTarget.uIndex); + gptUi->text("Chain Length: %u", ptIKComp->uChainLength); + gptUi->text("Iterations: %u", ptIKComp->uIterationCount); + gptUi->checkbox("Enabled", &ptIKComp->bEnabled); + gptUi->end_collapsing_header(); + } + } + + gptUi->end_child(); + } + + gptUi->end_window(); + } + + return tSelectedEntity; } \ No newline at end of file diff --git a/apps/helper_windows.h b/apps/helper_windows.h deleted file mode 100644 index cfb68c97..00000000 --- a/apps/helper_windows.h +++ /dev/null @@ -1,299 +0,0 @@ -#pragma once - -#include "pilotlight.h" -#include "pl_ui.h" -#include "pl_ecs_ext.h" -#include "pl_graphics_ext.h" -#include "pl_math.h" -#include "pl_ds.h" -#include "pl_string.h" - -static plEntity -pl_show_ecs_window(const plEcsI* ptECS, plComponentLibrary* ptLibrary, bool* pbShowWindow) -{ - static int iSelectedEntity = -1; - static plEntity tSelectedEntity = {UINT32_MAX, UINT32_MAX}; - if(pl_begin_window("Entities", pbShowWindow, false)) - { - const plVec2 tWindowSize = pl_get_window_size(); - const float pfRatios[] = {0.5f, 0.5f}; - pl_layout_row(PL_UI_LAYOUT_ROW_TYPE_DYNAMIC, 0.0f, 2, pfRatios); - pl_text("Entities"); - pl_text("Components"); - pl_layout_dynamic(0.0f, 1); - pl_separator(); - pl_layout_row(PL_UI_LAYOUT_ROW_TYPE_DYNAMIC, tWindowSize.y - 75.0f, 2, pfRatios); - - - if(pl_begin_child("Entities")) - { - const float pfRatiosInner[] = {1.0f}; - pl_layout_row(PL_UI_LAYOUT_ROW_TYPE_DYNAMIC, 0.0f, 1, pfRatiosInner); - - const uint32_t uEntityCount = pl_sb_size(ptLibrary->tTagComponentManager.sbtEntities); - plTagComponent* sbtTags = ptLibrary->tTagComponentManager.pComponents; - - plUiClipper tClipper = {(uint32_t)uEntityCount}; - while(pl_step_clipper(&tClipper)) - { - for(uint32_t i = tClipper.uDisplayStart; i < tClipper.uDisplayEnd; i++) - { - bool bSelected = (int)i == iSelectedEntity; - char atBuffer[1024] = {0}; - pl_sprintf(atBuffer, "%s ##%u", sbtTags[i].acName, i); - if(pl_selectable(atBuffer, &bSelected)) - { - if(bSelected) - { - iSelectedEntity = (int)i; - tSelectedEntity = ptLibrary->tTagComponentManager.sbtEntities[i]; - } - else - { - iSelectedEntity = -1; - tSelectedEntity.uIndex = UINT32_MAX; - tSelectedEntity.uGeneration = UINT32_MAX; - } - } - } - } - - pl_end_child(); - } - - if(pl_begin_child("Components")) - { - const float pfRatiosInner[] = {1.0f}; - pl_layout_row(PL_UI_LAYOUT_ROW_TYPE_DYNAMIC, 0.0f, 1, pfRatiosInner); - - if(iSelectedEntity != -1) - { - plTagComponent* ptTagComp = ptECS->get_component(ptLibrary, PL_COMPONENT_TYPE_TAG, tSelectedEntity); - plTransformComponent* ptTransformComp = ptECS->get_component(ptLibrary, PL_COMPONENT_TYPE_TRANSFORM, tSelectedEntity); - plMeshComponent* ptMeshComp = ptECS->get_component(ptLibrary, PL_COMPONENT_TYPE_MESH, tSelectedEntity); - plObjectComponent* ptObjectComp = ptECS->get_component(ptLibrary, PL_COMPONENT_TYPE_OBJECT, tSelectedEntity); - plHierarchyComponent* ptHierarchyComp = ptECS->get_component(ptLibrary, PL_COMPONENT_TYPE_HIERARCHY, tSelectedEntity); - plMaterialComponent* ptMaterialComp = ptECS->get_component(ptLibrary, PL_COMPONENT_TYPE_MATERIAL, tSelectedEntity); - plSkinComponent* ptSkinComp = ptECS->get_component(ptLibrary, PL_COMPONENT_TYPE_SKIN, tSelectedEntity); - plCameraComponent* ptCameraComp = ptECS->get_component(ptLibrary, PL_COMPONENT_TYPE_CAMERA, tSelectedEntity); - plAnimationComponent* ptAnimationComp = ptECS->get_component(ptLibrary, PL_COMPONENT_TYPE_ANIMATION, tSelectedEntity); - plInverseKinematicsComponent* ptIKComp = ptECS->get_component(ptLibrary, PL_COMPONENT_TYPE_INVERSE_KINEMATICS, tSelectedEntity); - plLightComponent* ptLightComp = ptECS->get_component(ptLibrary, PL_COMPONENT_TYPE_LIGHT, tSelectedEntity); - - pl_text("Entity: %u, %u", tSelectedEntity.uIndex, tSelectedEntity.uGeneration); - - if(ptTagComp && pl_collapsing_header("Tag")) - { - pl_text("Name: %s", ptTagComp->acName); - pl_end_collapsing_header(); - } - - if(ptTransformComp && pl_collapsing_header("Transform")) - { - pl_text("Scale: (%+0.3f, %+0.3f, %+0.3f)", ptTransformComp->tScale.x, ptTransformComp->tScale.y, ptTransformComp->tScale.z); - pl_text("Translation: (%+0.3f, %+0.3f, %+0.3f)", ptTransformComp->tTranslation.x, ptTransformComp->tTranslation.y, ptTransformComp->tTranslation.z); - pl_text("Rotation: (%+0.3f, %+0.3f, %+0.3f, %+0.3f)", ptTransformComp->tRotation.x, ptTransformComp->tRotation.y, ptTransformComp->tRotation.z, ptTransformComp->tRotation.w); - pl_vertical_spacing(); - pl_text("Local World: |%+0.3f, %+0.3f, %+0.3f, %+0.3f|", ptTransformComp->tWorld.col[0].x, ptTransformComp->tWorld.col[1].x, ptTransformComp->tWorld.col[2].x, ptTransformComp->tWorld.col[3].x); - pl_text(" |%+0.3f, %+0.3f, %+0.3f, %+0.3f|", ptTransformComp->tWorld.col[0].y, ptTransformComp->tWorld.col[1].y, ptTransformComp->tWorld.col[2].y, ptTransformComp->tWorld.col[3].y); - pl_text(" |%+0.3f, %+0.3f, %+0.3f, %+0.3f|", ptTransformComp->tWorld.col[0].z, ptTransformComp->tWorld.col[1].z, ptTransformComp->tWorld.col[2].z, ptTransformComp->tWorld.col[3].z); - pl_text(" |%+0.3f, %+0.3f, %+0.3f, %+0.3f|", ptTransformComp->tWorld.col[0].w, ptTransformComp->tWorld.col[1].w, ptTransformComp->tWorld.col[2].w, ptTransformComp->tWorld.col[3].w); - pl_end_collapsing_header(); - } - - if(ptMeshComp && pl_collapsing_header("Mesh")) - { - - plTagComponent* ptMaterialTagComp = ptECS->get_component(ptLibrary, PL_COMPONENT_TYPE_TAG, ptMeshComp->tMaterial); - plTagComponent* ptSkinTagComp = ptECS->get_component(ptLibrary, PL_COMPONENT_TYPE_TAG, ptMeshComp->tSkinComponent); - pl_text("Material: %s", ptMaterialTagComp->acName); - pl_text("Skin: %s", ptSkinTagComp ? ptSkinTagComp->acName : " "); - - pl_vertical_spacing(); - pl_text("Vertex Data (%u verts, %u idx)", pl_sb_size(ptMeshComp->sbtVertexPositions), pl_sb_size(ptMeshComp->sbuIndices)); - pl_indent(15.0f); - pl_text("%s Positions", ptMeshComp->ulVertexStreamMask & PL_MESH_FORMAT_FLAG_HAS_POSITION ? "ACTIVE" : " "); - pl_text("%s Normals", ptMeshComp->ulVertexStreamMask & PL_MESH_FORMAT_FLAG_HAS_NORMAL ? "ACTIVE" : " "); - pl_text("%s Tangents", ptMeshComp->ulVertexStreamMask & PL_MESH_FORMAT_FLAG_HAS_TANGENT ? "ACTIVE" : " "); - pl_text("%s Texture Coordinates 0", ptMeshComp->ulVertexStreamMask & PL_MESH_FORMAT_FLAG_HAS_TEXCOORD_0 ? "ACTIVE" : " "); - pl_text("%s Texture Coordinates 1", ptMeshComp->ulVertexStreamMask & PL_MESH_FORMAT_FLAG_HAS_TEXCOORD_1 ? "ACTIVE" : " "); - pl_text("%s Colors 0", ptMeshComp->ulVertexStreamMask & PL_MESH_FORMAT_FLAG_HAS_COLOR_0 ? "ACTIVE" : " "); - pl_text("%s Colors 1", ptMeshComp->ulVertexStreamMask & PL_MESH_FORMAT_FLAG_HAS_COLOR_1 ? "ACTIVE" : " "); - pl_text("%s Joints 0", ptMeshComp->ulVertexStreamMask & PL_MESH_FORMAT_FLAG_HAS_JOINTS_0 ? "ACTIVE" : " "); - pl_text("%s Joints 1", ptMeshComp->ulVertexStreamMask & PL_MESH_FORMAT_FLAG_HAS_JOINTS_1 ? "ACTIVE" : " "); - pl_text("%s Weights 0", ptMeshComp->ulVertexStreamMask & PL_MESH_FORMAT_FLAG_HAS_WEIGHTS_0 ? "ACTIVE" : " "); - pl_text("%s Weights 1", ptMeshComp->ulVertexStreamMask & PL_MESH_FORMAT_FLAG_HAS_WEIGHTS_1 ? "ACTIVE" : " "); - pl_unindent(15.0f); - pl_end_collapsing_header(); - } - - if(ptObjectComp && pl_collapsing_header("Object")) - { - plTagComponent* ptMeshTagComp = ptECS->get_component(ptLibrary, PL_COMPONENT_TYPE_TAG, ptObjectComp->tMesh); - plTagComponent* ptTransformTagComp = ptECS->get_component(ptLibrary, PL_COMPONENT_TYPE_TAG, ptObjectComp->tTransform); - pl_text("Mesh Entity: %s", ptMeshTagComp->acName); - pl_text("Transform Entity: %s, %u", ptTransformTagComp->acName, ptObjectComp->tTransform.uIndex); - pl_end_collapsing_header(); - } - - if(ptHierarchyComp && pl_collapsing_header("Hierarchy")) - { - plTagComponent* ptParentTagComp = ptECS->get_component(ptLibrary, PL_COMPONENT_TYPE_TAG, ptHierarchyComp->tParent); - pl_text("Parent Entity: %s , %u", ptParentTagComp->acName, ptHierarchyComp->tParent.uIndex); - pl_end_collapsing_header(); - } - - if(ptLightComp && pl_collapsing_header("Light")) - { - static const char* apcLightTypes[] = { - "PL_LIGHT_TYPE_DIRECTIONAL", - "PL_LIGHT_TYPE_POINT" - }; - pl_text("Type: %s", apcLightTypes[ptLightComp->tType]); - pl_text("Position: (%0.3f, %0.3f, %0.3f)", ptLightComp->tPosition.r, ptLightComp->tPosition.g, ptLightComp->tPosition.b); - pl_text("Color: (%0.3f, %0.3f, %0.3f)", ptLightComp->tColor.r, ptLightComp->tColor.g, ptLightComp->tColor.b); - pl_text("Direction: (%0.3f, %0.3f, %0.3f)", ptLightComp->tDirection.r, ptLightComp->tDirection.g, ptLightComp->tDirection.b); - pl_text("Intensity: %0.3f", ptLightComp->fIntensity); - pl_text("Cast Shadow: %s", ptLightComp->tFlags & PL_LIGHT_FLAG_CAST_SHADOW ? "true" : "false"); - } - - if(ptMaterialComp && pl_collapsing_header("Material")) - { - pl_text("Base Color: (%0.3f, %0.3f, %0.3f, %0.3f)", ptMaterialComp->tBaseColor.r, ptMaterialComp->tBaseColor.g, ptMaterialComp->tBaseColor.b, ptMaterialComp->tBaseColor.a); - pl_text("Alpha Cutoff: %0.3f", ptMaterialComp->fAlphaCutoff); - - static const char* apcBlendModeNames[] = - { - "PL_MATERIAL_BLEND_MODE_OPAQUE", - "PL_MATERIAL_BLEND_MODE_ALPHA", - "PL_MATERIAL_BLEND_MODE_PREMULTIPLIED", - "PL_MATERIAL_BLEND_MODE_ADDITIVE", - "PL_MATERIAL_BLEND_MODE_MULTIPLY" - }; - pl_text("Blend Mode: %s", apcBlendModeNames[ptMaterialComp->tBlendMode]); - - static const char* apcShaderNames[] = - { - "PL_SHADER_TYPE_PBR", - "PL_SHADER_TYPE_UNLIT", - "PL_SHADER_TYPE_CUSTOM" - }; - pl_text("Shader Type: %s", apcShaderNames[ptMaterialComp->tShaderType]); - pl_text("Double Sided: %s", ptMaterialComp->tFlags & PL_MATERIAL_FLAG_DOUBLE_SIDED ? "true" : "false"); - - pl_vertical_spacing(); - pl_text("Texture Maps"); - pl_indent(15.0f); - - static const char* apcTextureSlotNames[] = - { - "PL_TEXTURE_SLOT_BASE_COLOR_MAP", - "PL_TEXTURE_SLOT_NORMAL_MAP", - "PL_TEXTURE_SLOT_EMISSIVE_MAP", - "PL_TEXTURE_SLOT_OCCLUSION_MAP", - "PL_TEXTURE_SLOT_METAL_ROUGHNESS_MAP", - "PL_TEXTURE_SLOT_CLEARCOAT_MAP", - "PL_TEXTURE_SLOT_CLEARCOAT_ROUGHNESS_MAP", - "PL_TEXTURE_SLOT_CLEARCOAT_NORMAL_MAP", - "PL_TEXTURE_SLOT_SHEEN_COLOR_MAP", - "PL_TEXTURE_SLOT_SHEEN_ROUGHNESS_MAP", - "PL_TEXTURE_SLOT_TRANSMISSION_MAP", - "PL_TEXTURE_SLOT_SPECULAR_MAP", - "PL_TEXTURE_SLOT_SPECULAR_COLOR_MAP", - "PL_TEXTURE_SLOT_ANISOTROPY_MAP", - "PL_TEXTURE_SLOT_SURFACE_MAP", - "PL_TEXTURE_SLOT_IRIDESCENCE_MAP", - "PL_TEXTURE_SLOT_IRIDESCENCE_THICKNESS_MAP" - }; - - for(uint32_t i = 0; i < PL_TEXTURE_SLOT_COUNT; i++) - { - pl_text("%s: %s", apcTextureSlotNames[i], ptMaterialComp->atTextureMaps[i].acName[0] == 0 ? " " : "present"); - } - pl_unindent(15.0f); - pl_end_collapsing_header(); - } - - if(ptSkinComp && pl_collapsing_header("Skin")) - { - if(pl_tree_node("Joints")) - { - for(uint32_t i = 0; i < pl_sb_size(ptSkinComp->sbtJoints); i++) - { - plTagComponent* ptJointTagComp = ptECS->get_component(ptLibrary, PL_COMPONENT_TYPE_TAG, ptSkinComp->sbtJoints[i]); - pl_text("%s", ptJointTagComp->acName); - } - pl_tree_pop(); - } - pl_end_collapsing_header(); - } - - if(ptCameraComp && pl_collapsing_header("Camera")) - { - pl_text("Near Z: %+0.3f", ptCameraComp->fNearZ); - pl_text("Far Z: %+0.3f", ptCameraComp->fFarZ); - pl_text("Vertical Field of View: %+0.3f", ptCameraComp->fFieldOfView); - pl_text("Aspect Ratio: %+0.3f", ptCameraComp->fAspectRatio); - pl_text("Pitch: %+0.3f", ptCameraComp->fPitch); - pl_text("Yaw: %+0.3f", ptCameraComp->fYaw); - pl_text("Roll: %+0.3f", ptCameraComp->fRoll); - pl_text("Position: (%+0.3f, %+0.3f, %+0.3f)", ptCameraComp->tPos.x, ptCameraComp->tPos.y, ptCameraComp->tPos.z); - pl_text("Up: (%+0.3f, %+0.3f, %+0.3f)", ptCameraComp->_tUpVec.x, ptCameraComp->_tUpVec.y, ptCameraComp->_tUpVec.z); - pl_text("Forward: (%+0.3f, %+0.3f, %+0.3f)", ptCameraComp->_tForwardVec.x, ptCameraComp->_tForwardVec.y, ptCameraComp->_tForwardVec.z); - pl_text("Right: (%+0.3f, %+0.3f, %+0.3f)", ptCameraComp->_tRightVec.x, ptCameraComp->_tRightVec.y, ptCameraComp->_tRightVec.z); - pl_slider_float("Far Z Plane", &ptCameraComp->fFarZ, 10.0f, 400.0f); - pl_end_collapsing_header(); - } - - if(ptAnimationComp && pl_collapsing_header("Animation")) - { - bool bPlaying = ptAnimationComp->tFlags & PL_ANIMATION_FLAG_PLAYING; - bool bLooped = ptAnimationComp->tFlags & PL_ANIMATION_FLAG_LOOPED; - if(bLooped && bPlaying) - pl_text("Status: playing & looped"); - else if(bPlaying) - pl_text("Status: playing"); - else if(bLooped) - pl_text("Status: looped"); - else - pl_text("Status: not playing"); - if(pl_checkbox("Playing", &bPlaying)) - { - if(bPlaying) - ptAnimationComp->tFlags |= PL_ANIMATION_FLAG_PLAYING; - else - ptAnimationComp->tFlags &= ~PL_ANIMATION_FLAG_PLAYING; - } - if(pl_checkbox("Looped", &bLooped)) - { - if(bLooped) - ptAnimationComp->tFlags |= PL_ANIMATION_FLAG_LOOPED; - else - ptAnimationComp->tFlags &= ~PL_ANIMATION_FLAG_LOOPED; - } - pl_text("Start: %0.3f s", ptAnimationComp->fStart); - pl_text("End: %0.3f s", ptAnimationComp->fEnd); - pl_progress_bar(ptAnimationComp->fTimer / (ptAnimationComp->fEnd - ptAnimationComp->fStart), (plVec2){-1.0f, 0.0f}, NULL); - pl_text("Speed: %0.3f s", ptAnimationComp->fSpeed); - pl_end_collapsing_header(); - } - - if(ptIKComp && pl_collapsing_header("Inverse Kinematics")) - { - plTagComponent* ptTargetComp = ptECS->get_component(ptLibrary, PL_COMPONENT_TYPE_TAG, ptIKComp->tTarget); - pl_text("Target Entity: %s , %u", ptTargetComp->acName, ptIKComp->tTarget.uIndex); - pl_text("Chain Length: %u", ptIKComp->uChainLength); - pl_text("Iterations: %u", ptIKComp->uIterationCount); - pl_checkbox("Enabled", &ptIKComp->bEnabled); - pl_end_collapsing_header(); - } - } - - pl_end_child(); - } - - pl_end_window(); - } - - return tSelectedEntity; -} \ No newline at end of file diff --git a/examples/README.md b/examples/README.md index 93eea343..ce7049fa 100644 --- a/examples/README.md +++ b/examples/README.md @@ -37,15 +37,27 @@ Demonstrates: * loading APIs * hot reloading -## Example 2 - UI Library (example_2.c) +## Example 2 - Drawing Extension 2D (example_2.c) Demonstrates: * loading APIs * hot reloading * loading extensions -* UI library +* minimal use of graphics extension +* drawing extension (2D) -## Example 3 - 3D Debug Drawing (example_3.c) +## Example 3 - UI Extension (example_3.c) +Demonstrates: +* loading APIs +* hot reloading +* loading extensions +* minimal use of graphics extension +* drawing extension (2D) +* UI extension + +## Example 4 - Drawing Extension 3D (example_4.c) +Demonstrates: * loading APIs * hot reloading * loading extensions -* 3D debug drawing extension \ No newline at end of file +* minimal use of graphics extension +* drawing extension (3D) diff --git a/examples/example_0.c b/examples/example_0.c index c0f6bb46..b09cb0f7 100644 --- a/examples/example_0.c +++ b/examples/example_0.c @@ -6,6 +6,7 @@ /* Index of this file: // [SECTION] includes +// [SECTION] apis // [SECTION] pl_app_load // [SECTION] pl_app_shutdown // [SECTION] pl_app_resize @@ -18,7 +19,12 @@ Index of this file: #include #include "pilotlight.h" -#include "pl_ui.h" + +//----------------------------------------------------------------------------- +// [SECTION] apis +//----------------------------------------------------------------------------- + +const plIOI* gptIO = NULL; //----------------------------------------------------------------------------- // [SECTION] pl_app_load @@ -34,10 +40,8 @@ pl_app_load(plApiRegistryI* ptApiRegistry, void* pAppData) // between extensions & the runtime const plDataRegistryI* ptDataRegistry = ptApiRegistry->first(PL_API_DATA_REGISTRY); - // retrieve the UI context (provided by the runtime) and - // set it (required to use plIO for "talking" with runtime) - plUiContext* ptUIContext = ptDataRegistry->get_data("context"); - pl_set_context(ptUIContext); + // retrieve the IO API required to use plIO for "talking" with runtime) + gptIO = ptApiRegistry->first(PL_API_IO); // return optional application memory return NULL; @@ -71,7 +75,7 @@ PL_EXPORT void pl_app_update(void* pAppData) { - pl_new_frame(); // must be called once at the beginning of a frame + gptIO->new_frame(); // must be called once at the beginning of a frame static int iIteration = 0; @@ -80,7 +84,7 @@ pl_app_update(void* pAppData) // shutdown main event loop after 50 iterations if(iIteration == 50) { - plIO* ptIO = pl_get_io(); + plIO* ptIO = gptIO->get_io(); ptIO->bRunning = false; } } diff --git a/examples/example_1.c b/examples/example_1.c index a826d255..d4a617ed 100644 --- a/examples/example_1.c +++ b/examples/example_1.c @@ -22,7 +22,6 @@ Index of this file: #include #include // memset #include "pilotlight.h" -#include "pl_ui.h" #include "pl_os.h" // window api //----------------------------------------------------------------------------- @@ -38,6 +37,7 @@ typedef struct _plAppData // [SECTION] apis //----------------------------------------------------------------------------- +const plIOI* gptIO = NULL; const plWindowI* gptWindows = NULL; //----------------------------------------------------------------------------- @@ -54,21 +54,16 @@ pl_app_load(plApiRegistryI* ptApiRegistry, plAppData* ptAppData) // between extensions & the runtime const plDataRegistryI* ptDataRegistry = ptApiRegistry->first(PL_API_DATA_REGISTRY); - // retrieve the UI context (provided by the runtime) and - // set it (required to use plIO for "talking" with runtime & keyboard/mouse input) - plUiContext* ptUIContext = ptDataRegistry->get_data("context"); - pl_set_context(ptUIContext); - + // load required apis (NULL if not available) + gptIO = ptApiRegistry->first(PL_API_IO); + gptWindows = ptApiRegistry->first(PL_API_WINDOW); + // if "ptAppData" is a valid pointer, then this function is being called // during a hot reload. if(ptAppData) { printf("Hot reload!\n"); - // re-retrieve the windows API since we are now in - // a different dll/so - gptWindows = ptApiRegistry->first(PL_API_WINDOW); - // return the same memory again return ptAppData; } @@ -78,9 +73,6 @@ pl_app_load(plApiRegistryI* ptApiRegistry, plAppData* ptAppData) ptAppData = malloc(sizeof(plAppData)); memset(ptAppData, 0, sizeof(plAppData)); - // load required apis (NULL if not available) - gptWindows = ptApiRegistry->first(PL_API_WINDOW); - // use window API to create a window const plWindowDesc tWindowDesc = { .pcName = "Example 1", @@ -117,7 +109,7 @@ PL_EXPORT void pl_app_resize(plAppData* ptAppData) { // perform any operations required during a window resize - plIO* ptIO = pl_get_io(); + plIO* ptIO = gptIO->get_io(); printf("resize to %d, %d\n", (int)ptIO->afMainViewportSize[0], (int)ptIO->afMainViewportSize[1]); } @@ -129,10 +121,10 @@ PL_EXPORT void pl_app_update(plAppData* ptAppData) { - pl_new_frame(); // must be called once at the beginning of a frame + gptIO->new_frame(); // must be called once at the beginning of a frame // check for key press - if(pl_is_key_pressed(PL_KEY_P, true)) + if(gptIO->is_key_pressed(PL_KEY_P, true)) { printf("P key pressed!\n"); } diff --git a/examples/example_2.c b/examples/example_2.c index 3e22fd03..6c734e3d 100644 --- a/examples/example_2.c +++ b/examples/example_2.c @@ -3,8 +3,8 @@ - demonstrates loading APIs - demonstrates loading extensions - demonstrates hot reloading - - demonstrates UI library - demonstrates minimal use of graphics extension + - demonstrates drawing extension (2D) */ /* @@ -31,10 +31,10 @@ Index of this file: #include "pl_memory.h" #define PL_MATH_INCLUDE_FUNCTIONS #include "pl_math.h" -#include "pl_ui.h" // extensions #include "pl_graphics_ext.h" +#include "pl_draw_ext.h" //----------------------------------------------------------------------------- // [SECTION] structs @@ -49,10 +49,10 @@ typedef struct _plAppData bool bShowUiDemo; // drawing - plFontAtlas tFontAtlas; - plDrawList tAppDrawlist; - plDrawLayer* ptFGLayer; - plDrawLayer* ptBGLayer; + plFontAtlas tFontAtlas; + plDrawList2D* ptDrawlist; + plDrawLayer2D* ptFGLayer; + plDrawLayer2D* ptBGLayer; // graphics & sync objects plGraphics tGraphics; @@ -65,9 +65,11 @@ typedef struct _plAppData // [SECTION] apis //----------------------------------------------------------------------------- +const plIOI* gptIO = NULL; const plWindowI* gptWindows = NULL; const plGraphicsI* gptGfx = NULL; const plDeviceI* gptDevice = NULL; +const plDrawI* gptDraw = NULL; //----------------------------------------------------------------------------- // [SECTION] pl_app_load @@ -83,10 +85,6 @@ pl_app_load(plApiRegistryI* ptApiRegistry, plAppData* ptAppData) // between extensions & the runtime const plDataRegistryI* ptDataRegistry = ptApiRegistry->first(PL_API_DATA_REGISTRY); - // retrieve the UI context (provided by the runtime) and - // set it (required to use plIO for "talking" with runtime & keyboard/mouse input) - pl_set_context(ptDataRegistry->get_data("ui")); - // retrieve the memory context (provided by the runtime) and // set it to allow for memory tracking when using PL_ALLOC/PL_FREE pl_set_memory_context(ptDataRegistry->get_data(PL_CONTEXT_MEMORY)); @@ -102,9 +100,11 @@ pl_app_load(plApiRegistryI* ptApiRegistry, plAppData* ptAppData) // re-retrieve the apis since we are now in // a different dll/so - gptWindows = ptApiRegistry->first(PL_API_WINDOW); - gptGfx = ptApiRegistry->first(PL_API_GRAPHICS); - gptDevice = ptApiRegistry->first(PL_API_DEVICE); + gptIO = ptApiRegistry->first(PL_API_IO); + gptWindows = ptApiRegistry->first(PL_API_WINDOW); + gptGfx = ptApiRegistry->first(PL_API_GRAPHICS); + gptDevice = ptApiRegistry->first(PL_API_DEVICE); + gptDraw = ptApiRegistry->first(PL_API_DRAW); return ptAppData; } @@ -132,11 +132,14 @@ pl_app_load(plApiRegistryI* ptApiRegistry, plAppData* ptAppData) // load graphics extension (provides graphics & device apis) ptExtensionRegistry->load("pl_graphics_ext", NULL, NULL, false); + ptExtensionRegistry->load("pl_draw_ext", NULL, NULL, true); // load required apis (NULL if not available) + gptIO = ptApiRegistry->first(PL_API_IO); gptWindows = ptApiRegistry->first(PL_API_WINDOW); gptGfx = ptApiRegistry->first(PL_API_GRAPHICS); gptDevice = ptApiRegistry->first(PL_API_DEVICE); + gptDraw = ptApiRegistry->first(PL_API_DRAW); // use window API to create a window const plWindowDesc tWindowDesc = { @@ -154,17 +157,15 @@ pl_app_load(plApiRegistryI* ptApiRegistry, plAppData* ptAppData) }; gptGfx->initialize(ptAppData->ptWindow, &tGraphicsDesc, &ptAppData->tGraphics); - // setup ui - pl_add_default_font(&ptAppData->tFontAtlas); // Proggy.ttf w/ 13 pt - pl_build_font_atlas(&ptAppData->tFontAtlas); // generates font atlas data - gptGfx->setup_ui(&ptAppData->tGraphics, ptAppData->tGraphics.tMainRenderPass); // prepares any graphics backend specifics - gptGfx->create_font_atlas(&ptAppData->tFontAtlas); // creates font atlas texture - pl_set_default_font(&ptAppData->tFontAtlas.sbtFonts[0]); // sets default font to use for UI rendering + // setup draw + gptDraw->initialize(&ptAppData->tGraphics); + gptDraw->add_default_font(&ptAppData->tFontAtlas); + gptDraw->build_font_atlas(&ptAppData->tFontAtlas); // register our app drawlist - pl_register_drawlist(&ptAppData->tAppDrawlist); - ptAppData->ptFGLayer = pl_request_layer(&ptAppData->tAppDrawlist, "foreground layer"); - ptAppData->ptBGLayer = pl_request_layer(&ptAppData->tAppDrawlist, "background layer"); + ptAppData->ptDrawlist = gptDraw->request_2d_drawlist(); + ptAppData->ptFGLayer = gptDraw->request_2d_layer(ptAppData->ptDrawlist, "foreground layer"); + ptAppData->ptBGLayer = gptDraw->request_2d_layer(ptAppData->ptDrawlist, "background layer"); // create timeline semaphores to syncronize GPU work submission for(uint32_t i = 0; i < PL_FRAMES_IN_FLIGHT; i++) @@ -181,12 +182,10 @@ pl_app_load(plApiRegistryI* ptApiRegistry, plAppData* ptAppData) PL_EXPORT void pl_app_shutdown(plAppData* ptAppData) { - // cleanup - gptGfx->destroy_font_atlas(&ptAppData->tFontAtlas); // backend specific cleanup - pl_cleanup_font_atlas(&ptAppData->tFontAtlas); - // ensure GPU is finished before cleanup gptDevice->flush_device(&ptAppData->tGraphics.tDevice); + gptDraw->cleanup_font_atlas(&ptAppData->tFontAtlas); + gptDraw->cleanup(); gptGfx->cleanup(&ptAppData->tGraphics); gptWindows->destroy_window(ptAppData->ptWindow); pl_cleanup_profile_context(); @@ -214,6 +213,9 @@ pl_app_update(plAppData* ptAppData) { pl_begin_profile_frame(); + gptIO->new_frame(); + gptDraw->new_frame(); + // for convience plGraphics* ptGraphics = &ptAppData->tGraphics; @@ -225,45 +227,15 @@ pl_app_update(plAppData* ptAppData) return; } - pl_new_frame(); // must be called once at the beginning of a frame - - // create a UI window - if(pl_begin_window("Pilot Light", NULL, false)) - { - - const float pfRatios[] = {1.0f}; - pl_layout_row(PL_UI_LAYOUT_ROW_TYPE_DYNAMIC, 0.0f, 1, pfRatios); - if(pl_collapsing_header("Information")) - { - pl_text("Pilot Light %s", PILOTLIGHT_VERSION); - pl_text("Pilot Light UI %s", PL_UI_VERSION); - pl_text("Pilot Light DS %s", PL_DS_VERSION); - pl_end_collapsing_header(); - } - - if(pl_collapsing_header("User Interface")) - { - pl_checkbox("UI Demo", &ptAppData->bShowUiDemo); - pl_end_collapsing_header(); - } - pl_end_window(); - } - - if(ptAppData->bShowUiDemo) - pl_show_demo_window(&ptAppData->bShowUiDemo); - // drawing API usage - pl_add_circle(ptAppData->ptFGLayer, (plVec2){120.0f, 120.0f}, 50.0f, (plVec4){1.0f, 1.0f, 0.0f, 1.0f}, 0, 1.0f); - pl_add_circle_filled(ptAppData->ptBGLayer, (plVec2){100.0f, 100.0f}, 25.0f, (plVec4){1.0f, 0.0f, 1.0f, 1.0f}, 24); - - //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~UI & drawing prep~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + gptDraw->add_circle(ptAppData->ptFGLayer, (plVec2){120.0f, 120.0f}, 50.0f, (plVec4){1.0f, 1.0f, 0.0f, 1.0f}, 0, 1.0f); + gptDraw->add_circle_filled(ptAppData->ptBGLayer, (plVec2){100.0f, 100.0f}, 25.0f, (plVec4){1.0f, 0.0f, 1.0f, 1.0f}, 24); - // build UI render data (and submits layers in correct order) - pl_render(); + //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~drawing prep~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // submit our draw layers - pl_submit_layer(ptAppData->ptBGLayer); - pl_submit_layer(ptAppData->ptFGLayer); + gptDraw->submit_2d_layer(ptAppData->ptBGLayer); + gptDraw->submit_2d_layer(ptAppData->ptFGLayer); //~~~~~~~~~~~~~~~~~~~~~~~~begin recording command buffer~~~~~~~~~~~~~~~~~~~~~~~ @@ -283,9 +255,8 @@ pl_app_update(plAppData* ptAppData) plRenderEncoder tEncoder = gptGfx->begin_render_pass(ptGraphics, &tCommandBuffer, ptGraphics->tMainRenderPass); // submit drawlists - gptGfx->draw_lists(ptGraphics, tEncoder, 1, pl_get_draw_list(NULL)); - gptGfx->draw_lists(ptGraphics, tEncoder, 1, pl_get_debug_draw_list(NULL)); - gptGfx->draw_lists(ptGraphics, tEncoder, 1, &ptAppData->tAppDrawlist); + plIO* ptIO = gptIO->get_io(); + gptDraw->submit_2d_drawlist(ptAppData->ptDrawlist, tEncoder, ptIO->afMainViewportSize[0], ptIO->afMainViewportSize[1], 1); // end render pass gptGfx->end_render_pass(&tEncoder); diff --git a/examples/example_3.c b/examples/example_3.c index ca7c4d50..4f522e7d 100644 --- a/examples/example_3.c +++ b/examples/example_3.c @@ -3,14 +3,8 @@ - demonstrates loading APIs - demonstrates loading extensions - demonstrates hot reloading - - demonstrates UI library - - demonstrates 3d debug drawing extension - demonstrates minimal use of graphics extension - - Notes: - - We are performing offscreen rendering for this example because - the graphics extension does not current expose renderpass options - and does not have a depth buffer by default. + - demonstrates ui extension */ /* @@ -18,12 +12,10 @@ Index of this file: // [SECTION] includes // [SECTION] structs // [SECTION] apis -// [SECTION] helper function declarations // [SECTION] pl_app_load // [SECTION] pl_app_shutdown // [SECTION] pl_app_resize // [SECTION] pl_app_update -// [SECTION] helper function definitions */ //----------------------------------------------------------------------------- @@ -39,39 +31,16 @@ Index of this file: #include "pl_memory.h" #define PL_MATH_INCLUDE_FUNCTIONS #include "pl_math.h" -#include "pl_ui.h" // extensions #include "pl_graphics_ext.h" -#include "pl_draw_3d_ext.h" -#include "pl_gpu_allocators_ext.h" +#include "pl_draw_ext.h" +#include "pl_ui_ext.h" //----------------------------------------------------------------------------- // [SECTION] structs //----------------------------------------------------------------------------- -typedef struct _plCamera -{ - plVec3 tPos; - float fNearZ; - float fFarZ; - float fFieldOfView; - float fAspectRatio; // width/height - plMat4 tViewMat; // cached - plMat4 tProjMat; // cached - plMat4 tTransformMat; // cached - - // rotations - float fPitch; // rotation about right vector - float fYaw; // rotation about up vector - float fRoll; // rotation about forward vector - - // direction vectors - plVec3 _tUpVec; - plVec3 _tForwardVec; - plVec3 _tRightVec; -} plCamera; - typedef struct _plAppData { // window @@ -79,53 +48,29 @@ typedef struct _plAppData // ui options bool bShowUiDemo; + bool bShowUiDebug; + bool bShowUiStyle; // drawing - plFontAtlas tFontAtlas; - plDrawList tAppDrawlist; - plDrawLayer* ptFGLayer; - - // 3d drawing - plCamera tCamera; - plDrawList3D* pt3dDrawlist; + plFontAtlas tFontAtlas; // graphics & sync objects plGraphics tGraphics; plSemaphoreHandle atSempahore[PL_FRAMES_IN_FLIGHT]; uint64_t aulNextTimelineValue[PL_FRAMES_IN_FLIGHT]; - // offscreen rendering - bool bResize; - plSamplerHandle tDefaultSampler; - plRenderPassHandle tOffscreenRenderPass; - plVec2 tOffscreenSize; - plTextureId atColorTextureId[PL_FRAMES_IN_FLIGHT]; // used by UI to draw - plTextureHandle atColorTexture[PL_FRAMES_IN_FLIGHT]; - plTextureHandle atDepthTexture[PL_FRAMES_IN_FLIGHT]; - } plAppData; //----------------------------------------------------------------------------- // [SECTION] apis //----------------------------------------------------------------------------- -const plWindowI* gptWindows = NULL; -const plGraphicsI* gptGfx = NULL; -const plDeviceI* gptDevice = NULL; -const plDraw3dI* gptDraw3d = NULL; -const plGPUAllocatorsI* gptGpuAllocators = NULL; - -//----------------------------------------------------------------------------- -// [SECTION] helper function declarations -//----------------------------------------------------------------------------- - -void resize_offscreen_resources(plAppData* ptAppData); - -// camera helpers -void camera_translate(plCamera*, float fDx, float fDy, float fDz); -void camera_rotate (plCamera*, float fDPitch, float fDYaw); -void camera_rotate (plCamera*, float fDPitch, float fDYaw); -void camera_update (plCamera*); +const plIOI* gptIO = NULL; +const plWindowI* gptWindows = NULL; +const plGraphicsI* gptGfx = NULL; +const plDeviceI* gptDevice = NULL; +const plDrawI* gptDraw = NULL; +const plUiI* gptUi = NULL; //----------------------------------------------------------------------------- // [SECTION] pl_app_load @@ -141,10 +86,6 @@ pl_app_load(plApiRegistryI* ptApiRegistry, plAppData* ptAppData) // between extensions & the runtime const plDataRegistryI* ptDataRegistry = ptApiRegistry->first(PL_API_DATA_REGISTRY); - // retrieve the UI context (provided by the runtime) and - // set it (required to use plIO for "talking" with runtime & keyboard/mouse input) - pl_set_context(ptDataRegistry->get_data("ui")); - // retrieve the memory context (provided by the runtime) and // set it to allow for memory tracking when using PL_ALLOC/PL_FREE pl_set_memory_context(ptDataRegistry->get_data(PL_CONTEXT_MEMORY)); @@ -160,11 +101,12 @@ pl_app_load(plApiRegistryI* ptApiRegistry, plAppData* ptAppData) // re-retrieve the apis since we are now in // a different dll/so - gptWindows = ptApiRegistry->first(PL_API_WINDOW); - gptGfx = ptApiRegistry->first(PL_API_GRAPHICS); - gptDevice = ptApiRegistry->first(PL_API_DEVICE); - gptDraw3d = ptApiRegistry->first(PL_API_DRAW_3D); - gptGpuAllocators = ptApiRegistry->first(PL_API_GPU_ALLOCATORS); + gptIO = ptApiRegistry->first(PL_API_IO); + gptWindows = ptApiRegistry->first(PL_API_WINDOW); + gptGfx = ptApiRegistry->first(PL_API_GRAPHICS); + gptDevice = ptApiRegistry->first(PL_API_DEVICE); + gptDraw = ptApiRegistry->first(PL_API_DRAW); + gptUi = ptApiRegistry->first(PL_API_UI); return ptAppData; } @@ -190,28 +132,28 @@ pl_app_load(plApiRegistryI* ptApiRegistry, plAppData* ptAppData) // retrieve extension registry const plExtensionRegistryI* ptExtensionRegistry = ptApiRegistry->first(PL_API_EXTENSION_REGISTRY); - // load extensions (makes their APIs available) - ptExtensionRegistry->load("pl_graphics_ext", NULL, NULL, false); - ptExtensionRegistry->load("pl_gpu_allocators_ext", NULL, NULL, false); - ptExtensionRegistry->load("pl_draw_3d_ext", NULL, NULL, true); + // load graphics extension (provides graphics & device apis) + ptExtensionRegistry->load("pl_graphics_ext", NULL, NULL, false); + ptExtensionRegistry->load("pl_draw_ext", NULL, NULL, true); + ptExtensionRegistry->load("pl_ui_ext", NULL, NULL, true); // load required apis (NULL if not available) - gptWindows = ptApiRegistry->first(PL_API_WINDOW); - gptGfx = ptApiRegistry->first(PL_API_GRAPHICS); - gptDevice = ptApiRegistry->first(PL_API_DEVICE); - gptDraw3d = ptApiRegistry->first(PL_API_DRAW_3D); - gptGpuAllocators = ptApiRegistry->first(PL_API_GPU_ALLOCATORS); + gptIO = ptApiRegistry->first(PL_API_IO); + gptWindows = ptApiRegistry->first(PL_API_WINDOW); + gptGfx = ptApiRegistry->first(PL_API_GRAPHICS); + gptDevice = ptApiRegistry->first(PL_API_DEVICE); + gptDraw = ptApiRegistry->first(PL_API_DRAW); + gptUi = ptApiRegistry->first(PL_API_UI); // use window API to create a window const plWindowDesc tWindowDesc = { - .pcName = "Example 3", + .pcName = "Example 2", .iXPos = 200, .iYPos = 200, .uWidth = 600, .uHeight = 600, }; ptAppData->ptWindow = gptWindows->create_window(&tWindowDesc); - ptAppData->tOffscreenSize = (plVec2){600.0f, 600.0f}; // initialize graphics system const plGraphicsDesc tGraphicsDesc = { @@ -219,149 +161,18 @@ pl_app_load(plApiRegistryI* ptApiRegistry, plAppData* ptAppData) }; gptGfx->initialize(ptAppData->ptWindow, &tGraphicsDesc, &ptAppData->tGraphics); - // for convience - plGraphics* ptGraphics = &ptAppData->tGraphics; - plDevice* ptDevice = &ptGraphics->tDevice; - - // initialize 3D drawing system - gptDraw3d->initialize(ptGraphics); - ptAppData->pt3dDrawlist = gptDraw3d->request_drawlist(); - ptAppData->tCamera = (plCamera){ - .tPos = {5.0f, 10.0f, 10.0f}, - .fNearZ = 0.01f, - .fFarZ = 50.0f, - .fFieldOfView = PL_PI_3, - .fAspectRatio = ptAppData->tOffscreenSize.x / ptAppData->tOffscreenSize.y, - .fYaw = PL_PI + PL_PI_4, - .fPitch = -PL_PI_4, - }; - camera_update(&ptAppData->tCamera); + // setup draw + gptDraw->initialize(&ptAppData->tGraphics); + gptDraw->add_default_font(&ptAppData->tFontAtlas); + gptDraw->build_font_atlas(&ptAppData->tFontAtlas); // setup ui - pl_add_default_font(&ptAppData->tFontAtlas); // Proggy.ttf w/ 13 pt - pl_build_font_atlas(&ptAppData->tFontAtlas); // generates font atlas data - gptGfx->setup_ui(ptGraphics, ptAppData->tGraphics.tMainRenderPass); // prepares any graphics backend specifics - gptGfx->create_font_atlas(&ptAppData->tFontAtlas); // creates font atlas texture - pl_set_default_font(&ptAppData->tFontAtlas.sbtFonts[0]); // sets default font to use for UI rendering - - // register our app drawlist - pl_register_drawlist(&ptAppData->tAppDrawlist); - ptAppData->ptFGLayer = pl_request_layer(&ptAppData->tAppDrawlist, "foreground layer"); + gptUi->initialize(); + gptUi->set_default_font(&ptAppData->tFontAtlas.sbtFonts[0]); // create timeline semaphores to syncronize GPU work submission for(uint32_t i = 0; i < PL_FRAMES_IN_FLIGHT; i++) - ptAppData->atSempahore[i] = gptDevice->create_semaphore(ptDevice, false); - - // create default sampler - const plSamplerDesc tSamplerDesc = { - .tFilter = PL_FILTER_LINEAR, - .fMinMip = 0.0f, - .fMaxMip = 64.0f, - .tVerticalWrap = PL_WRAP_MODE_WRAP, - .tHorizontalWrap = PL_WRAP_MODE_WRAP - }; - ptAppData->tDefaultSampler = gptDevice->create_sampler(ptDevice, &tSamplerDesc, "default sampler"); - - // create offscreen per-frame resources - - const plTextureDesc tColorTextureDesc = { - .tDimensions = {ptAppData->tOffscreenSize.x, ptAppData->tOffscreenSize.y, 1}, - .tFormat = PL_FORMAT_R32G32B32A32_FLOAT, - .uLayers = 1, - .uMips = 1, - .tType = PL_TEXTURE_TYPE_2D, - .tUsage = PL_TEXTURE_USAGE_SAMPLED | PL_TEXTURE_USAGE_COLOR_ATTACHMENT, - .tInitialUsage = PL_TEXTURE_USAGE_SAMPLED - }; - - const plTextureDesc tDepthTextureDesc = { - .tDimensions = {ptAppData->tOffscreenSize.x, ptAppData->tOffscreenSize.y, 1}, - .tFormat = PL_FORMAT_D32_FLOAT_S8_UINT, - .uLayers = 1, - .uMips = 1, - .tType = PL_TEXTURE_TYPE_2D, - .tUsage = PL_TEXTURE_USAGE_DEPTH_STENCIL_ATTACHMENT, - .tInitialUsage = PL_TEXTURE_USAGE_DEPTH_STENCIL_ATTACHMENT - }; - - plRenderPassAttachments atAttachmentSets[PL_FRAMES_IN_FLIGHT] = {0}; - - for(uint32_t i = 0; i < PL_FRAMES_IN_FLIGHT; i++) - { - // create textures - ptAppData->atColorTexture[i] = gptDevice->create_texture(ptDevice, &tColorTextureDesc, "color texture"); - ptAppData->atDepthTexture[i] = gptDevice->create_texture(ptDevice, &tDepthTextureDesc, "depth texture"); - - // retrieve textures - plTexture* ptColorTexture = gptDevice->get_texture(ptDevice, ptAppData->atColorTexture[i]); - plTexture* ptDepthTexture = gptDevice->get_texture(ptDevice, ptAppData->atDepthTexture[i]); - - plDeviceMemoryAllocatorI* ptAllocator = gptGpuAllocators->get_local_dedicated_allocator(ptDevice); - - // allocate memory - const plDeviceMemoryAllocation tColorAllocation = ptAllocator->allocate(ptAllocator->ptInst, - ptColorTexture->tMemoryRequirements.uMemoryTypeBits, - ptColorTexture->tMemoryRequirements.ulSize, - ptColorTexture->tMemoryRequirements.ulAlignment, - "color texture memory"); - - const plDeviceMemoryAllocation tDepthAllocation = ptAllocator->allocate(ptAllocator->ptInst, - ptDepthTexture->tMemoryRequirements.uMemoryTypeBits, - ptDepthTexture->tMemoryRequirements.ulSize, - ptDepthTexture->tMemoryRequirements.ulAlignment, - "depth texture memory"); - - // bind memory - gptDevice->bind_texture_to_memory(ptDevice, ptAppData->atColorTexture[i], &tColorAllocation); - gptDevice->bind_texture_to_memory(ptDevice, ptAppData->atDepthTexture[i], &tDepthAllocation); - - // get UI texture handle - ptAppData->atColorTextureId[i] = gptGfx->get_ui_texture_handle(ptGraphics, ptAppData->atColorTexture[i], ptAppData->tDefaultSampler); - - // add textures to attachment set for render pass - atAttachmentSets[i].atViewAttachments[0] = ptAppData->atDepthTexture[i]; - atAttachmentSets[i].atViewAttachments[1] = ptAppData->atColorTexture[i]; - } - - // create offscreen renderpass layout - const plRenderPassLayoutDescription tRenderPassLayoutDesc = { - .atRenderTargets = { - { .tFormat = PL_FORMAT_D32_FLOAT_S8_UINT }, // depth buffer - { .tFormat = PL_FORMAT_R32G32B32A32_FLOAT } // color - }, - .uSubpassCount = 1, - .atSubpasses = { - { - .uRenderTargetCount = 2, - .auRenderTargets = {0, 1}, - }, - } - }; - - // create offscreen renderpass - const plRenderPassDescription tRenderPassDesc = { - .tLayout = gptDevice->create_render_pass_layout(ptDevice, &tRenderPassLayoutDesc), - .tDepthTarget = { - .tLoadOp = PL_LOAD_OP_CLEAR, - .tStoreOp = PL_STORE_OP_DONT_CARE, - .tStencilLoadOp = PL_LOAD_OP_CLEAR, - .tStencilStoreOp = PL_STORE_OP_DONT_CARE, - .tCurrentUsage = PL_TEXTURE_USAGE_DEPTH_STENCIL_ATTACHMENT, - .tNextUsage = PL_TEXTURE_USAGE_DEPTH_STENCIL_ATTACHMENT, - .fClearZ = 1.0f - }, - .atColorTargets = { - { - .tLoadOp = PL_LOAD_OP_CLEAR, - .tStoreOp = PL_STORE_OP_STORE, - .tCurrentUsage = PL_TEXTURE_USAGE_SAMPLED, - .tNextUsage = PL_TEXTURE_USAGE_SAMPLED, - .tClearColor = {0.0f, 0.0f, 0.0f, 1.0f} - } - }, - .tDimensions = {.x = ptAppData->tOffscreenSize.x, .y = ptAppData->tOffscreenSize.y} - }; - ptAppData->tOffscreenRenderPass = gptDevice->create_render_pass(&ptGraphics->tDevice, &tRenderPassDesc, atAttachmentSets); + ptAppData->atSempahore[i] = gptDevice->create_semaphore(&ptAppData->tGraphics.tDevice, false); // return app memory return ptAppData; @@ -374,14 +185,11 @@ pl_app_load(plApiRegistryI* ptApiRegistry, plAppData* ptAppData) PL_EXPORT void pl_app_shutdown(plAppData* ptAppData) { - // cleanup - gptGfx->destroy_font_atlas(&ptAppData->tFontAtlas); // backend specific cleanup - pl_cleanup_font_atlas(&ptAppData->tFontAtlas); - // ensure GPU is finished before cleanup gptDevice->flush_device(&ptAppData->tGraphics.tDevice); - gptDraw3d->cleanup(); - gptGpuAllocators->cleanup_allocators(&ptAppData->tGraphics.tDevice); + gptDraw->cleanup_font_atlas(&ptAppData->tFontAtlas); + gptUi->cleanup(); + gptDraw->cleanup(); gptGfx->cleanup(&ptAppData->tGraphics); gptWindows->destroy_window(ptAppData->ptWindow); pl_cleanup_profile_context(); @@ -398,8 +206,6 @@ pl_app_resize(plAppData* ptAppData) { // perform any operations required during a window resize gptGfx->resize(&ptAppData->tGraphics); // recreates swapchain - ptAppData->bResize = true; - } //----------------------------------------------------------------------------- @@ -411,18 +217,12 @@ pl_app_update(plAppData* ptAppData) { pl_begin_profile_frame(); + gptIO->new_frame(); + gptDraw->new_frame(); + gptUi->new_frame(); + // for convience plGraphics* ptGraphics = &ptAppData->tGraphics; - plIO* ptIO = pl_get_io(); - - if(ptAppData->bResize) - { - plDevice* ptDevice = &ptGraphics->tDevice; - ptAppData->tOffscreenSize.x = ptIO->afMainViewportSize[0]; - ptAppData->tOffscreenSize.y = ptIO->afMainViewportSize[1]; - resize_offscreen_resources(ptAppData); - ptAppData->bResize = false; - } // begin new frame if(!gptGfx->begin_frame(ptGraphics)) @@ -432,311 +232,86 @@ pl_app_update(plAppData* ptAppData) return; } - static const float fCameraTravelSpeed = 4.0f; - static const float fCameraRotationSpeed = 0.005f; - - bool bOwnKeyboard = ptIO->bWantCaptureKeyboard; - plCamera* ptCamera = &ptAppData->tCamera; - if(!bOwnKeyboard) - { - // camera space - if(pl_is_key_down(PL_KEY_W)) camera_translate(ptCamera, 0.0f, 0.0f, fCameraTravelSpeed * ptIO->fDeltaTime); - if(pl_is_key_down(PL_KEY_S)) camera_translate(ptCamera, 0.0f, 0.0f, -fCameraTravelSpeed* ptIO->fDeltaTime); - if(pl_is_key_down(PL_KEY_A)) camera_translate(ptCamera, -fCameraTravelSpeed * ptIO->fDeltaTime, 0.0f, 0.0f); - if(pl_is_key_down(PL_KEY_D)) camera_translate(ptCamera, fCameraTravelSpeed * ptIO->fDeltaTime, 0.0f, 0.0f); - - // world space - if(pl_is_key_down(PL_KEY_F)) { camera_translate(ptCamera, 0.0f, -fCameraTravelSpeed * ptIO->fDeltaTime, 0.0f); } - if(pl_is_key_down(PL_KEY_R)) { camera_translate(ptCamera, 0.0f, fCameraTravelSpeed * ptIO->fDeltaTime, 0.0f); } - } - - bool bOwnMouse = ptIO->bWantCaptureMouse; - if(!bOwnMouse && pl_is_mouse_dragging(PL_MOUSE_BUTTON_LEFT, 1.0f)) - { - const plVec2 tMouseDelta = pl_get_mouse_drag_delta(PL_MOUSE_BUTTON_LEFT, 1.0f); - camera_rotate(ptCamera, -tMouseDelta.y * fCameraRotationSpeed, -tMouseDelta.x * fCameraRotationSpeed); - pl_reset_mouse_drag_delta(PL_MOUSE_BUTTON_LEFT); - } - camera_update(ptCamera); - - gptDraw3d->new_frame(); + // NOTE: UI code can be placed anywhere between the UI "new_frame" & "render" - pl_new_frame(); // must be called once at the beginning of a frame - - // create a UI window - if(pl_begin_window("Pilot Light", NULL, false)) + //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~UI~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + if(gptUi->begin_window("Pilot Light", NULL, false)) { const float pfRatios[] = {1.0f}; - pl_layout_row(PL_UI_LAYOUT_ROW_TYPE_DYNAMIC, 0.0f, 1, pfRatios); - if(pl_collapsing_header("Information")) + gptUi->layout_row(PL_UI_LAYOUT_ROW_TYPE_DYNAMIC, 0.0f, 1, pfRatios); + if(gptUi->collapsing_header("Information")) { - pl_text("Pilot Light %s", PILOTLIGHT_VERSION); - pl_text("Pilot Light UI %s", PL_UI_VERSION); - pl_text("Pilot Light DS %s", PL_DS_VERSION); - pl_end_collapsing_header(); + + gptUi->text("Pilot Light %s", PILOTLIGHT_VERSION); + gptUi->text("Pilot Light UI %s", PL_UI_EXT_VERSION); + gptUi->text("Pilot Light DS %s", PL_DS_VERSION); + #ifdef PL_METAL_BACKEND + gptUi->text("Graphics Backend: Metal"); + #elif PL_VULKAN_BACKEND + gptUi->text("Graphics Backend: Vulkan"); + #else + gptUi->text("Graphics Backend: Unknown"); + #endif + + gptUi->end_collapsing_header(); } - - if(pl_collapsing_header("User Interface")) + if(gptUi->collapsing_header("User Interface")) { - pl_checkbox("UI Demo", &ptAppData->bShowUiDemo); - pl_end_collapsing_header(); + gptUi->checkbox("UI Debug", &ptAppData->bShowUiDebug); + gptUi->checkbox("UI Demo", &ptAppData->bShowUiDemo); + gptUi->checkbox("UI Style", &ptAppData->bShowUiStyle); + gptUi->end_collapsing_header(); } - pl_end_window(); + gptUi->end_window(); } if(ptAppData->bShowUiDemo) - pl_show_demo_window(&ptAppData->bShowUiDemo); - - // add full screen quad for offscreen render - pl_add_image(ptAppData->ptFGLayer, ptAppData->atColorTextureId[ptGraphics->uCurrentFrameIndex], (plVec2){0}, (plVec2){ptIO->afMainViewportSize[0], ptIO->afMainViewportSize[1]}); - - // 3d drawing API usage - const plMat4 tOrigin = pl_identity_mat4(); - gptDraw3d->add_transform(ptAppData->pt3dDrawlist, &tOrigin, 10.0f, 0.2f); - - gptDraw3d->add_triangle_filled(ptAppData->pt3dDrawlist, - (plVec3){1.0f, 1.0f, 1.0f}, - (plVec3){4.0f, 1.0f, 1.0f}, - (plVec3){1.0f, 4.0f, 1.0f}, - (plVec4){1.0f, 1.0f, 0.0f, 0.75f}); - - gptDraw3d->add_triangle_filled(ptAppData->pt3dDrawlist, - (plVec3){1.0f, 1.0f, 3.0f}, - (plVec3){4.0f, 1.0f, 3.0f}, - (plVec3){1.0f, 4.0f, 3.0f}, - (plVec4){1.0f, 0.5f, 0.3f, 0.75f}); + gptUi->show_demo_window(&ptAppData->bShowUiDemo); + + if(ptAppData->bShowUiStyle) + gptUi->show_style_editor_window(&ptAppData->bShowUiStyle); - //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~UI & drawing prep~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + if(ptAppData->bShowUiDebug) + gptUi->show_debug_window(&ptAppData->bShowUiDebug); - // submit our draw layers - pl_submit_layer(ptAppData->ptFGLayer); - - // build UI render data (and submits layers in correct order) - pl_render(); - - //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~command buffer 0~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + //~~~~~~~~~~~~~~~~~~~~~~~~begin recording command buffer~~~~~~~~~~~~~~~~~~~~~~~ // expected timeline semaphore values uint64_t ulValue0 = ptAppData->aulNextTimelineValue[ptGraphics->uCurrentFrameIndex]; uint64_t ulValue1 = ulValue0 + 1; - uint64_t ulValue2 = ulValue0 + 2; - ptAppData->aulNextTimelineValue[ptGraphics->uCurrentFrameIndex] = ulValue2; + ptAppData->aulNextTimelineValue[ptGraphics->uCurrentFrameIndex] = ulValue1; - const plBeginCommandInfo tBeginInfo0 = { + const plBeginCommandInfo tBeginInfo = { .uWaitSemaphoreCount = 1, .atWaitSempahores = {ptAppData->atSempahore[ptGraphics->uCurrentFrameIndex]}, .auWaitSemaphoreValues = {ulValue0}, }; - plCommandBuffer tCommandBuffer0 = gptGfx->begin_command_recording(ptGraphics, &tBeginInfo0); - - // begin offscreen renderpass - plRenderEncoder tEncoder0 = gptGfx->begin_render_pass(ptGraphics, &tCommandBuffer0, ptAppData->tOffscreenRenderPass); - - const plMat4 tMVP = pl_mul_mat4(&ptCamera->tProjMat, &ptCamera->tViewMat); - gptDraw3d->submit_drawlist(ptAppData->pt3dDrawlist, - tEncoder0, - ptAppData->tOffscreenSize.x, - ptAppData->tOffscreenSize.y, - &tMVP, - PL_3D_DRAW_FLAG_DEPTH_TEST | PL_3D_DRAW_FLAG_DEPTH_WRITE, 1); - - // end offscreen render pass - gptGfx->end_render_pass(&tEncoder0); - - // end recording - gptGfx->end_command_recording(ptGraphics, &tCommandBuffer0); - - const plSubmitInfo tSubmitInfo0 = { - .uSignalSemaphoreCount = 1, - .atSignalSempahores = {ptAppData->atSempahore[ptGraphics->uCurrentFrameIndex]}, - .auSignalSemaphoreValues = {ulValue1}, - }; - gptGfx->submit_command_buffer(ptGraphics, &tCommandBuffer0, &tSubmitInfo0); - - //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~command buffer 1~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - const plBeginCommandInfo tBeginInfo1 = { - .uWaitSemaphoreCount = 1, - .atWaitSempahores = {ptAppData->atSempahore[ptGraphics->uCurrentFrameIndex]}, - .auWaitSemaphoreValues = {ulValue1}, - }; - plCommandBuffer tCommandBuffer1 = gptGfx->begin_command_recording(ptGraphics, &tBeginInfo1); + plCommandBuffer tCommandBuffer = gptGfx->begin_command_recording(ptGraphics, &tBeginInfo); // begin main renderpass (directly to swapchain) - plRenderEncoder tEncoder1 = gptGfx->begin_render_pass(ptGraphics, &tCommandBuffer1, ptGraphics->tMainRenderPass); + plRenderEncoder tEncoder = gptGfx->begin_render_pass(ptGraphics, &tCommandBuffer, ptGraphics->tMainRenderPass); + + // submits UI drawlist/layers + plIO* ptIO = gptIO->get_io(); + gptUi->render(tEncoder, ptIO->afMainViewportSize[0], ptIO->afMainViewportSize[1], 1); - // submit drawlists - gptGfx->draw_lists(ptGraphics, tEncoder1, 1, &ptAppData->tAppDrawlist); - gptGfx->draw_lists(ptGraphics, tEncoder1, 1, pl_get_draw_list(NULL)); - gptGfx->draw_lists(ptGraphics, tEncoder1, 1, pl_get_debug_draw_list(NULL)); - // end render pass - gptGfx->end_render_pass(&tEncoder1); + gptGfx->end_render_pass(&tEncoder); // end recording - gptGfx->end_command_recording(ptGraphics, &tCommandBuffer1); + gptGfx->end_command_recording(ptGraphics, &tCommandBuffer); //~~~~~~~~~~~~~~~~~~~~~~~~~~submit work to GPU & present~~~~~~~~~~~~~~~~~~~~~~~ - const plSubmitInfo tSubmitInfo1 = { + const plSubmitInfo tSubmitInfo = { .uSignalSemaphoreCount = 1, .atSignalSempahores = {ptAppData->atSempahore[ptGraphics->uCurrentFrameIndex]}, - .auSignalSemaphoreValues = {ulValue2}, + .auSignalSemaphoreValues = {ulValue1}, }; - if(!gptGfx->present(ptGraphics, &tCommandBuffer1, &tSubmitInfo1)) + if(!gptGfx->present(ptGraphics, &tCommandBuffer, &tSubmitInfo)) gptGfx->resize(ptGraphics); pl_end_profile_frame(); -} - -//----------------------------------------------------------------------------- -// [SECTION] helper function declarations -//----------------------------------------------------------------------------- - -static inline float -wrap_angle(float tTheta) -{ - static const float f2Pi = 2.0f * PL_PI; - const float fMod = fmodf(tTheta, f2Pi); - if (fMod > PL_PI) return fMod - f2Pi; - else if (fMod < -PL_PI) return fMod + f2Pi; - return fMod; -} - -void -camera_translate(plCamera* ptCamera, float fDx, float fDy, float fDz) -{ - ptCamera->tPos = pl_add_vec3(ptCamera->tPos, pl_mul_vec3_scalarf(ptCamera->_tRightVec, fDx)); - ptCamera->tPos = pl_add_vec3(ptCamera->tPos, pl_mul_vec3_scalarf(ptCamera->_tForwardVec, fDz)); - ptCamera->tPos.y += fDy; -} - -void -camera_rotate(plCamera* ptCamera, float fDPitch, float fDYaw) -{ - ptCamera->fPitch += fDPitch; - ptCamera->fYaw += fDYaw; - - ptCamera->fYaw = wrap_angle(ptCamera->fYaw); - ptCamera->fPitch = pl_clampf(0.995f * -PL_PI_2, ptCamera->fPitch, 0.995f * PL_PI_2); -} - -void -camera_update(plCamera* ptCamera) -{ - //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~update view~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - // world space - static const plVec4 tOriginalUpVec = {0.0f, 1.0f, 0.0f, 0.0f}; - static const plVec4 tOriginalForwardVec = {0.0f, 0.0f, 1.0f, 0.0f}; - static const plVec4 tOriginalRightVec = {-1.0f, 0.0f, 0.0f, 0.0f}; - - const plMat4 tXRotMat = pl_mat4_rotate_vec3(ptCamera->fPitch, tOriginalRightVec.xyz); - const plMat4 tYRotMat = pl_mat4_rotate_vec3(ptCamera->fYaw, tOriginalUpVec.xyz); - const plMat4 tZRotMat = pl_mat4_rotate_vec3(ptCamera->fRoll, tOriginalForwardVec.xyz); - const plMat4 tTranslate = pl_mat4_translate_vec3((plVec3){ptCamera->tPos.x, ptCamera->tPos.y, ptCamera->tPos.z}); - - // rotations: rotY * rotX * rotZ - plMat4 tRotations = pl_mul_mat4t(&tXRotMat, &tZRotMat); - tRotations = pl_mul_mat4t(&tYRotMat, &tRotations); - - // update camera vectors - ptCamera->_tRightVec = pl_norm_vec4(pl_mul_mat4_vec4(&tRotations, tOriginalRightVec)).xyz; - ptCamera->_tUpVec = pl_norm_vec4(pl_mul_mat4_vec4(&tRotations, tOriginalUpVec)).xyz; - ptCamera->_tForwardVec = pl_norm_vec4(pl_mul_mat4_vec4(&tRotations, tOriginalForwardVec)).xyz; - - // update camera transform: translate * rotate - ptCamera->tTransformMat = pl_mul_mat4t(&tTranslate, &tRotations); - - // update camera view matrix - ptCamera->tViewMat = pl_mat4t_invert(&ptCamera->tTransformMat); - - // flip x & y so camera looks down +z and remains right handed (+x to the right) - const plMat4 tFlipXY = pl_mat4_scale_xyz(-1.0f, -1.0f, 1.0f); - ptCamera->tViewMat = pl_mul_mat4t(&tFlipXY, &ptCamera->tViewMat); - - //~~~~~~~~~~~~~~~~~~~~~~~~~~~update projection~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - const float fInvtanHalfFovy = 1.0f / tanf(ptCamera->fFieldOfView / 2.0f); - ptCamera->tProjMat.col[0].x = fInvtanHalfFovy / ptCamera->fAspectRatio; - ptCamera->tProjMat.col[1].y = fInvtanHalfFovy; - ptCamera->tProjMat.col[2].z = ptCamera->fFarZ / (ptCamera->fFarZ - ptCamera->fNearZ); - ptCamera->tProjMat.col[2].w = 1.0f; - ptCamera->tProjMat.col[3].z = -ptCamera->fNearZ * ptCamera->fFarZ / (ptCamera->fFarZ - ptCamera->fNearZ); - ptCamera->tProjMat.col[3].w = 0.0f; -} - -void -resize_offscreen_resources(plAppData* ptAppData) -{ - plGraphics* ptGraphics = &ptAppData->tGraphics; - plDevice* ptDevice = &ptGraphics->tDevice; - - plIO* ptIO = pl_get_io(); - ptAppData->tCamera.fAspectRatio = ptIO->afMainViewportSize[0] / ptIO->afMainViewportSize[1]; - - const plTextureDesc tColorTextureDesc = { - .tDimensions = {ptAppData->tOffscreenSize.x, ptAppData->tOffscreenSize.y, 1}, - .tFormat = PL_FORMAT_R32G32B32A32_FLOAT, - .uLayers = 1, - .uMips = 1, - .tType = PL_TEXTURE_TYPE_2D, - .tUsage = PL_TEXTURE_USAGE_SAMPLED | PL_TEXTURE_USAGE_COLOR_ATTACHMENT, - .tInitialUsage = PL_TEXTURE_USAGE_SAMPLED - }; - - const plTextureDesc tDepthTextureDesc = { - .tDimensions = {ptAppData->tOffscreenSize.x, ptAppData->tOffscreenSize.y, 1}, - .tFormat = PL_FORMAT_D32_FLOAT_S8_UINT, - .uLayers = 1, - .uMips = 1, - .tType = PL_TEXTURE_TYPE_2D, - .tUsage = PL_TEXTURE_USAGE_DEPTH_STENCIL_ATTACHMENT, - .tInitialUsage = PL_TEXTURE_USAGE_DEPTH_STENCIL_ATTACHMENT - }; - - plRenderPassAttachments atAttachmentSets[PL_FRAMES_IN_FLIGHT] = {0}; - - for(uint32_t i = 0; i < PL_FRAMES_IN_FLIGHT; i++) - { - // queue old resources for deletion - gptDevice->queue_texture_for_deletion(ptDevice, ptAppData->atColorTexture[i]); - gptDevice->queue_texture_for_deletion(ptDevice, ptAppData->atDepthTexture[i]); - - // create new textures - ptAppData->atColorTexture[i] = gptDevice->create_texture(ptDevice, &tColorTextureDesc, "color texture"); - ptAppData->atDepthTexture[i] = gptDevice->create_texture(ptDevice, &tDepthTextureDesc, "depth texture"); - - // retrieve textures - plTexture* ptColorTexture = gptDevice->get_texture(ptDevice, ptAppData->atColorTexture[i]); - plTexture* ptDepthTexture = gptDevice->get_texture(ptDevice, ptAppData->atDepthTexture[i]); - - plDeviceMemoryAllocatorI* ptAllocator = gptGpuAllocators->get_local_dedicated_allocator(ptDevice); - - // allocate memory - const plDeviceMemoryAllocation tColorAllocation = ptAllocator->allocate(ptAllocator->ptInst, - ptColorTexture->tMemoryRequirements.uMemoryTypeBits, - ptColorTexture->tMemoryRequirements.ulSize, - ptColorTexture->tMemoryRequirements.ulAlignment, - "color texture memory"); - - const plDeviceMemoryAllocation tDepthAllocation = ptAllocator->allocate(ptAllocator->ptInst, - ptDepthTexture->tMemoryRequirements.uMemoryTypeBits, - ptDepthTexture->tMemoryRequirements.ulSize, - ptDepthTexture->tMemoryRequirements.ulAlignment, - "depth texture memory"); - - // bind memory - gptDevice->bind_texture_to_memory(ptDevice, ptAppData->atColorTexture[i], &tColorAllocation); - gptDevice->bind_texture_to_memory(ptDevice, ptAppData->atDepthTexture[i], &tDepthAllocation); - - // get UI texture handle - ptAppData->atColorTextureId[i] = gptGfx->get_ui_texture_handle(ptGraphics, ptAppData->atColorTexture[i], ptAppData->tDefaultSampler); - - // add textures to attachment set for render pass - atAttachmentSets[i].atViewAttachments[0] = ptAppData->atDepthTexture[i]; - atAttachmentSets[i].atViewAttachments[1] = ptAppData->atColorTexture[i]; - } - gptDevice->update_render_pass_attachments(ptDevice, ptAppData->tOffscreenRenderPass, ptAppData->tOffscreenSize, atAttachmentSets); } \ No newline at end of file diff --git a/examples/example_4.c b/examples/example_4.c new file mode 100644 index 00000000..6cd26085 --- /dev/null +++ b/examples/example_4.c @@ -0,0 +1,696 @@ +/* + example_4.c + - demonstrates loading APIs + - demonstrates loading extensions + - demonstrates hot reloading + - demonstrates drawing extension (2D & 3D) + - demonstrates minimal use of graphics extension + + Notes: + - We are performing offscreen rendering for this example because + the graphics extension does not current expose renderpass options + and does not have a depth buffer by default. +*/ + +/* +Index of this file: +// [SECTION] includes +// [SECTION] structs +// [SECTION] apis +// [SECTION] helper function declarations +// [SECTION] pl_app_load +// [SECTION] pl_app_shutdown +// [SECTION] pl_app_resize +// [SECTION] pl_app_update +// [SECTION] helper function definitions +*/ + +//----------------------------------------------------------------------------- +// [SECTION] includes +//----------------------------------------------------------------------------- + +#include +#include "pilotlight.h" +#include "pl_profile.h" +#include "pl_log.h" +#include "pl_ds.h" +#include "pl_os.h" +#include "pl_memory.h" +#define PL_MATH_INCLUDE_FUNCTIONS +#include "pl_math.h" + +// extensions +#include "pl_graphics_ext.h" +#include "pl_draw_ext.h" +#include "pl_gpu_allocators_ext.h" + +//----------------------------------------------------------------------------- +// [SECTION] structs +//----------------------------------------------------------------------------- + +typedef struct _plCamera +{ + plVec3 tPos; + float fNearZ; + float fFarZ; + float fFieldOfView; + float fAspectRatio; // width/height + plMat4 tViewMat; // cached + plMat4 tProjMat; // cached + plMat4 tTransformMat; // cached + + // rotations + float fPitch; // rotation about right vector + float fYaw; // rotation about up vector + float fRoll; // rotation about forward vector + + // direction vectors + plVec3 _tUpVec; + plVec3 _tForwardVec; + plVec3 _tRightVec; +} plCamera; + +typedef struct _plAppData +{ + // window + plWindow* ptWindow; + + // ui options + bool bShowUiDemo; + + // drawing + plFontAtlas tFontAtlas; + plDrawList2D* ptAppDrawlist; + plDrawLayer2D* ptFGLayer; + + // 3d drawing + plCamera tCamera; + plDrawList3D* pt3dDrawlist; + + // graphics & sync objects + plGraphics tGraphics; + plSemaphoreHandle atSempahore[PL_FRAMES_IN_FLIGHT]; + uint64_t aulNextTimelineValue[PL_FRAMES_IN_FLIGHT]; + + // offscreen rendering + bool bResize; + plSamplerHandle tDefaultSampler; + plRenderPassHandle tOffscreenRenderPass; + plVec2 tOffscreenSize; + plTextureHandle atColorTexture[PL_FRAMES_IN_FLIGHT]; + plTextureHandle atDepthTexture[PL_FRAMES_IN_FLIGHT]; + +} plAppData; + +//----------------------------------------------------------------------------- +// [SECTION] apis +//----------------------------------------------------------------------------- + +const plIOI* gptIO = NULL; +const plWindowI* gptWindows = NULL; +const plGraphicsI* gptGfx = NULL; +const plDeviceI* gptDevice = NULL; +const plDrawI* gptDraw = NULL; +const plGPUAllocatorsI* gptGpuAllocators = NULL; + +//----------------------------------------------------------------------------- +// [SECTION] helper function declarations +//----------------------------------------------------------------------------- + +void resize_offscreen_resources(plAppData* ptAppData); + +// camera helpers +void camera_translate(plCamera*, float fDx, float fDy, float fDz); +void camera_rotate (plCamera*, float fDPitch, float fDYaw); +void camera_rotate (plCamera*, float fDPitch, float fDYaw); +void camera_update (plCamera*); + +//----------------------------------------------------------------------------- +// [SECTION] pl_app_load +//----------------------------------------------------------------------------- + +PL_EXPORT void* +pl_app_load(plApiRegistryI* ptApiRegistry, plAppData* ptAppData) +{ + // NOTE: on first load, "pAppData" will be NULL but on reloads + // it will be the value returned from this function + + // retrieve the data registry API, this is the API used for sharing data + // between extensions & the runtime + const plDataRegistryI* ptDataRegistry = ptApiRegistry->first(PL_API_DATA_REGISTRY); + + // retrieve the memory context (provided by the runtime) and + // set it to allow for memory tracking when using PL_ALLOC/PL_FREE + pl_set_memory_context(ptDataRegistry->get_data(PL_CONTEXT_MEMORY)); + + // if "ptAppData" is a valid pointer, then this function is being called + // during a hot reload. + if(ptAppData) + { + // set contexts again since we are now in a + // differenct dll/so + pl_set_log_context(ptDataRegistry->get_data("log")); + pl_set_profile_context(ptDataRegistry->get_data("profile")); + + // re-retrieve the apis since we are now in + // a different dll/so + gptIO = ptApiRegistry->first(PL_API_IO); + gptWindows = ptApiRegistry->first(PL_API_WINDOW); + gptGfx = ptApiRegistry->first(PL_API_GRAPHICS); + gptDevice = ptApiRegistry->first(PL_API_DEVICE); + gptDraw = ptApiRegistry->first(PL_API_DRAW); + gptGpuAllocators = ptApiRegistry->first(PL_API_GPU_ALLOCATORS); + + return ptAppData; + } + + // this path is taken only during first load, so we + // allocate app memory here (using PL_ALLOC for memory tracking) + ptAppData = PL_ALLOC(sizeof(plAppData)); + memset(ptAppData, 0, sizeof(plAppData)); + + // create profiling & logging contexts (used by extension here) + plProfileContext* ptProfileCtx = pl_create_profile_context(); + plLogContext* ptLogCtx = pl_create_log_context(); + + // add log channel (ignoring the return here) + pl_add_log_channel("Default", PL_CHANNEL_TYPE_CONSOLE); + pl_log_info("Setup logging"); + + // add these to data registry so they can be retrieved by extension + // and subsequent app reloads + ptDataRegistry->set_data("profile", ptProfileCtx); + ptDataRegistry->set_data("log", ptLogCtx); + + // retrieve extension registry + const plExtensionRegistryI* ptExtensionRegistry = ptApiRegistry->first(PL_API_EXTENSION_REGISTRY); + + // load extensions (makes their APIs available) + ptExtensionRegistry->load("pl_graphics_ext", NULL, NULL, false); + ptExtensionRegistry->load("pl_gpu_allocators_ext", NULL, NULL, false); + ptExtensionRegistry->load("pl_draw_ext", NULL, NULL, true); + + // load required apis (NULL if not available) + gptIO = ptApiRegistry->first(PL_API_IO); + gptWindows = ptApiRegistry->first(PL_API_WINDOW); + gptGfx = ptApiRegistry->first(PL_API_GRAPHICS); + gptDevice = ptApiRegistry->first(PL_API_DEVICE); + gptDraw = ptApiRegistry->first(PL_API_DRAW); + gptGpuAllocators = ptApiRegistry->first(PL_API_GPU_ALLOCATORS); + + // use window API to create a window + const plWindowDesc tWindowDesc = { + .pcName = "Example 3", + .iXPos = 200, + .iYPos = 200, + .uWidth = 600, + .uHeight = 600, + }; + ptAppData->ptWindow = gptWindows->create_window(&tWindowDesc); + ptAppData->tOffscreenSize = (plVec2){600.0f, 600.0f}; + + // initialize graphics system + const plGraphicsDesc tGraphicsDesc = { + .bEnableValidation = true + }; + gptGfx->initialize(ptAppData->ptWindow, &tGraphicsDesc, &ptAppData->tGraphics); + + // setup draw + gptDraw->initialize(&ptAppData->tGraphics); + gptDraw->add_default_font(&ptAppData->tFontAtlas); + gptDraw->build_font_atlas(&ptAppData->tFontAtlas); + + // request drawlists + ptAppData->ptAppDrawlist = gptDraw->request_2d_drawlist(); + ptAppData->ptFGLayer = gptDraw->request_2d_layer(ptAppData->ptAppDrawlist, "foreground layer"); + ptAppData->pt3dDrawlist = gptDraw->request_3d_drawlist(); + + // for convience + plGraphics* ptGraphics = &ptAppData->tGraphics; + plDevice* ptDevice = &ptGraphics->tDevice; + + // create camera + ptAppData->tCamera = (plCamera){ + .tPos = {5.0f, 10.0f, 10.0f}, + .fNearZ = 0.01f, + .fFarZ = 50.0f, + .fFieldOfView = PL_PI_3, + .fAspectRatio = ptAppData->tOffscreenSize.x / ptAppData->tOffscreenSize.y, + .fYaw = PL_PI + PL_PI_4, + .fPitch = -PL_PI_4, + }; + camera_update(&ptAppData->tCamera); + + // create timeline semaphores to syncronize GPU work submission + for(uint32_t i = 0; i < PL_FRAMES_IN_FLIGHT; i++) + ptAppData->atSempahore[i] = gptDevice->create_semaphore(ptDevice, false); + + // create default sampler + const plSamplerDesc tSamplerDesc = { + .tFilter = PL_FILTER_LINEAR, + .fMinMip = 0.0f, + .fMaxMip = 64.0f, + .tVerticalWrap = PL_WRAP_MODE_WRAP, + .tHorizontalWrap = PL_WRAP_MODE_WRAP + }; + ptAppData->tDefaultSampler = gptDevice->create_sampler(ptDevice, &tSamplerDesc, "default sampler"); + + // create offscreen per-frame resources + + const plTextureDesc tColorTextureDesc = { + .tDimensions = {ptAppData->tOffscreenSize.x, ptAppData->tOffscreenSize.y, 1}, + .tFormat = PL_FORMAT_R32G32B32A32_FLOAT, + .uLayers = 1, + .uMips = 1, + .tType = PL_TEXTURE_TYPE_2D, + .tUsage = PL_TEXTURE_USAGE_SAMPLED | PL_TEXTURE_USAGE_COLOR_ATTACHMENT, + .tInitialUsage = PL_TEXTURE_USAGE_SAMPLED + }; + + const plTextureDesc tDepthTextureDesc = { + .tDimensions = {ptAppData->tOffscreenSize.x, ptAppData->tOffscreenSize.y, 1}, + .tFormat = PL_FORMAT_D32_FLOAT_S8_UINT, + .uLayers = 1, + .uMips = 1, + .tType = PL_TEXTURE_TYPE_2D, + .tUsage = PL_TEXTURE_USAGE_DEPTH_STENCIL_ATTACHMENT, + .tInitialUsage = PL_TEXTURE_USAGE_DEPTH_STENCIL_ATTACHMENT + }; + + plRenderPassAttachments atAttachmentSets[PL_FRAMES_IN_FLIGHT] = {0}; + + for(uint32_t i = 0; i < PL_FRAMES_IN_FLIGHT; i++) + { + // create textures + ptAppData->atColorTexture[i] = gptDevice->create_texture(ptDevice, &tColorTextureDesc, "color texture"); + ptAppData->atDepthTexture[i] = gptDevice->create_texture(ptDevice, &tDepthTextureDesc, "depth texture"); + + // retrieve textures + plTexture* ptColorTexture = gptDevice->get_texture(ptDevice, ptAppData->atColorTexture[i]); + plTexture* ptDepthTexture = gptDevice->get_texture(ptDevice, ptAppData->atDepthTexture[i]); + + plDeviceMemoryAllocatorI* ptAllocator = gptGpuAllocators->get_local_dedicated_allocator(ptDevice); + + // allocate memory + const plDeviceMemoryAllocation tColorAllocation = ptAllocator->allocate(ptAllocator->ptInst, + ptColorTexture->tMemoryRequirements.uMemoryTypeBits, + ptColorTexture->tMemoryRequirements.ulSize, + ptColorTexture->tMemoryRequirements.ulAlignment, + "color texture memory"); + + const plDeviceMemoryAllocation tDepthAllocation = ptAllocator->allocate(ptAllocator->ptInst, + ptDepthTexture->tMemoryRequirements.uMemoryTypeBits, + ptDepthTexture->tMemoryRequirements.ulSize, + ptDepthTexture->tMemoryRequirements.ulAlignment, + "depth texture memory"); + + // bind memory + gptDevice->bind_texture_to_memory(ptDevice, ptAppData->atColorTexture[i], &tColorAllocation); + gptDevice->bind_texture_to_memory(ptDevice, ptAppData->atDepthTexture[i], &tDepthAllocation); + + // add textures to attachment set for render pass + atAttachmentSets[i].atViewAttachments[0] = ptAppData->atDepthTexture[i]; + atAttachmentSets[i].atViewAttachments[1] = ptAppData->atColorTexture[i]; + } + + // create offscreen renderpass layout + const plRenderPassLayoutDescription tRenderPassLayoutDesc = { + .atRenderTargets = { + { .tFormat = PL_FORMAT_D32_FLOAT_S8_UINT }, // depth buffer + { .tFormat = PL_FORMAT_R32G32B32A32_FLOAT } // color + }, + .uSubpassCount = 1, + .atSubpasses = { + { + .uRenderTargetCount = 2, + .auRenderTargets = {0, 1}, + }, + } + }; + + // create offscreen renderpass + const plRenderPassDescription tRenderPassDesc = { + .tLayout = gptDevice->create_render_pass_layout(ptDevice, &tRenderPassLayoutDesc), + .tDepthTarget = { + .tLoadOp = PL_LOAD_OP_CLEAR, + .tStoreOp = PL_STORE_OP_DONT_CARE, + .tStencilLoadOp = PL_LOAD_OP_CLEAR, + .tStencilStoreOp = PL_STORE_OP_DONT_CARE, + .tCurrentUsage = PL_TEXTURE_USAGE_DEPTH_STENCIL_ATTACHMENT, + .tNextUsage = PL_TEXTURE_USAGE_DEPTH_STENCIL_ATTACHMENT, + .fClearZ = 1.0f + }, + .atColorTargets = { + { + .tLoadOp = PL_LOAD_OP_CLEAR, + .tStoreOp = PL_STORE_OP_STORE, + .tCurrentUsage = PL_TEXTURE_USAGE_SAMPLED, + .tNextUsage = PL_TEXTURE_USAGE_SAMPLED, + .tClearColor = {0.0f, 0.0f, 0.0f, 1.0f} + } + }, + .tDimensions = {.x = ptAppData->tOffscreenSize.x, .y = ptAppData->tOffscreenSize.y} + }; + ptAppData->tOffscreenRenderPass = gptDevice->create_render_pass(&ptGraphics->tDevice, &tRenderPassDesc, atAttachmentSets); + + // return app memory + return ptAppData; +} + +//----------------------------------------------------------------------------- +// [SECTION] pl_app_shutdown +//----------------------------------------------------------------------------- + +PL_EXPORT void +pl_app_shutdown(plAppData* ptAppData) +{ + // ensure GPU is finished before cleanup + gptDevice->flush_device(&ptAppData->tGraphics.tDevice); + gptDraw->cleanup_font_atlas(&ptAppData->tFontAtlas); + gptDraw->cleanup(); + gptGpuAllocators->cleanup_allocators(&ptAppData->tGraphics.tDevice); + gptGfx->cleanup(&ptAppData->tGraphics); + gptWindows->destroy_window(ptAppData->ptWindow); + pl_cleanup_profile_context(); + pl_cleanup_log_context(); + PL_FREE(ptAppData); +} + +//----------------------------------------------------------------------------- +// [SECTION] pl_app_resize +//----------------------------------------------------------------------------- + +PL_EXPORT void +pl_app_resize(plAppData* ptAppData) +{ + // perform any operations required during a window resize + gptGfx->resize(&ptAppData->tGraphics); // recreates swapchain + ptAppData->bResize = true; + +} + +//----------------------------------------------------------------------------- +// [SECTION] pl_app_update +//----------------------------------------------------------------------------- + +PL_EXPORT void +pl_app_update(plAppData* ptAppData) +{ + pl_begin_profile_frame(); + + gptIO->new_frame(); + gptDraw->new_frame(); + + // for convience + plGraphics* ptGraphics = &ptAppData->tGraphics; + plIO* ptIO = gptIO->get_io(); + + if(ptAppData->bResize) + { + plDevice* ptDevice = &ptGraphics->tDevice; + ptAppData->tOffscreenSize.x = ptIO->afMainViewportSize[0]; + ptAppData->tOffscreenSize.y = ptIO->afMainViewportSize[1]; + resize_offscreen_resources(ptAppData); + ptAppData->bResize = false; + } + + // begin new frame + if(!gptGfx->begin_frame(ptGraphics)) + { + gptGfx->resize(ptGraphics); + pl_end_profile_frame(); + return; + } + + static const float fCameraTravelSpeed = 4.0f; + static const float fCameraRotationSpeed = 0.005f; + + bool bOwnKeyboard = ptIO->bWantCaptureKeyboard; + plCamera* ptCamera = &ptAppData->tCamera; + if(!bOwnKeyboard) + { + // camera space + if(gptIO->is_key_down(PL_KEY_W)) camera_translate(ptCamera, 0.0f, 0.0f, fCameraTravelSpeed * ptIO->fDeltaTime); + if(gptIO->is_key_down(PL_KEY_S)) camera_translate(ptCamera, 0.0f, 0.0f, -fCameraTravelSpeed* ptIO->fDeltaTime); + if(gptIO->is_key_down(PL_KEY_A)) camera_translate(ptCamera, -fCameraTravelSpeed * ptIO->fDeltaTime, 0.0f, 0.0f); + if(gptIO->is_key_down(PL_KEY_D)) camera_translate(ptCamera, fCameraTravelSpeed * ptIO->fDeltaTime, 0.0f, 0.0f); + + // world space + if(gptIO->is_key_down(PL_KEY_F)) { camera_translate(ptCamera, 0.0f, -fCameraTravelSpeed * ptIO->fDeltaTime, 0.0f); } + if(gptIO->is_key_down(PL_KEY_R)) { camera_translate(ptCamera, 0.0f, fCameraTravelSpeed * ptIO->fDeltaTime, 0.0f); } + } + + bool bOwnMouse = ptIO->bWantCaptureMouse; + if(!bOwnMouse && gptIO->is_mouse_dragging(PL_MOUSE_BUTTON_LEFT, 1.0f)) + { + const plVec2 tMouseDelta = gptIO->get_mouse_drag_delta(PL_MOUSE_BUTTON_LEFT, 1.0f); + camera_rotate(ptCamera, -tMouseDelta.y * fCameraRotationSpeed, -tMouseDelta.x * fCameraRotationSpeed); + gptIO->reset_mouse_drag_delta(PL_MOUSE_BUTTON_LEFT); + } + camera_update(ptCamera); + + // add full screen quad for offscreen render + gptDraw->add_image(ptAppData->ptFGLayer, ptAppData->atColorTexture[ptGraphics->uCurrentFrameIndex], (plVec2){0}, (plVec2){ptIO->afMainViewportSize[0], ptIO->afMainViewportSize[1]}); + + // 3d drawing API usage + const plMat4 tOrigin = pl_identity_mat4(); + gptDraw->add_3d_transform(ptAppData->pt3dDrawlist, &tOrigin, 10.0f, 0.2f); + + gptDraw->add_3d_triangle_filled(ptAppData->pt3dDrawlist, + (plVec3){1.0f, 1.0f, 1.0f}, + (plVec3){4.0f, 1.0f, 1.0f}, + (plVec3){1.0f, 4.0f, 1.0f}, + (plVec4){1.0f, 1.0f, 0.0f, 0.75f}); + + gptDraw->add_3d_triangle_filled(ptAppData->pt3dDrawlist, + (plVec3){1.0f, 1.0f, 3.0f}, + (plVec3){4.0f, 1.0f, 3.0f}, + (plVec3){1.0f, 4.0f, 3.0f}, + (plVec4){1.0f, 0.5f, 0.3f, 0.75f}); + + //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~drawing prep~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + // submit our draw layers + gptDraw->submit_2d_layer(ptAppData->ptFGLayer); + + //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~command buffer 0~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + // expected timeline semaphore values + uint64_t ulValue0 = ptAppData->aulNextTimelineValue[ptGraphics->uCurrentFrameIndex]; + uint64_t ulValue1 = ulValue0 + 1; + uint64_t ulValue2 = ulValue0 + 2; + ptAppData->aulNextTimelineValue[ptGraphics->uCurrentFrameIndex] = ulValue2; + + const plBeginCommandInfo tBeginInfo0 = { + .uWaitSemaphoreCount = 1, + .atWaitSempahores = {ptAppData->atSempahore[ptGraphics->uCurrentFrameIndex]}, + .auWaitSemaphoreValues = {ulValue0}, + }; + plCommandBuffer tCommandBuffer0 = gptGfx->begin_command_recording(ptGraphics, &tBeginInfo0); + + // begin offscreen renderpass + plRenderEncoder tEncoder0 = gptGfx->begin_render_pass(ptGraphics, &tCommandBuffer0, ptAppData->tOffscreenRenderPass); + + const plMat4 tMVP = pl_mul_mat4(&ptCamera->tProjMat, &ptCamera->tViewMat); + gptDraw->submit_3d_drawlist(ptAppData->pt3dDrawlist, + tEncoder0, + ptAppData->tOffscreenSize.x, + ptAppData->tOffscreenSize.y, + &tMVP, + PL_DRAW_FLAG_DEPTH_TEST | PL_DRAW_FLAG_DEPTH_WRITE, 1); + + // end offscreen render pass + gptGfx->end_render_pass(&tEncoder0); + + // end recording + gptGfx->end_command_recording(ptGraphics, &tCommandBuffer0); + + const plSubmitInfo tSubmitInfo0 = { + .uSignalSemaphoreCount = 1, + .atSignalSempahores = {ptAppData->atSempahore[ptGraphics->uCurrentFrameIndex]}, + .auSignalSemaphoreValues = {ulValue1}, + }; + gptGfx->submit_command_buffer(ptGraphics, &tCommandBuffer0, &tSubmitInfo0); + + //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~command buffer 1~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + const plBeginCommandInfo tBeginInfo1 = { + .uWaitSemaphoreCount = 1, + .atWaitSempahores = {ptAppData->atSempahore[ptGraphics->uCurrentFrameIndex]}, + .auWaitSemaphoreValues = {ulValue1}, + }; + plCommandBuffer tCommandBuffer1 = gptGfx->begin_command_recording(ptGraphics, &tBeginInfo1); + + // begin main renderpass (directly to swapchain) + plRenderEncoder tEncoder1 = gptGfx->begin_render_pass(ptGraphics, &tCommandBuffer1, ptGraphics->tMainRenderPass); + + // submit drawlists + gptDraw->submit_2d_drawlist(ptAppData->ptAppDrawlist, tEncoder1, ptIO->afMainViewportSize[0], ptIO->afMainViewportSize[1], 1); + + + // end render pass + gptGfx->end_render_pass(&tEncoder1); + + // end recording + gptGfx->end_command_recording(ptGraphics, &tCommandBuffer1); + + //~~~~~~~~~~~~~~~~~~~~~~~~~~submit work to GPU & present~~~~~~~~~~~~~~~~~~~~~~~ + + const plSubmitInfo tSubmitInfo1 = { + .uSignalSemaphoreCount = 1, + .atSignalSempahores = {ptAppData->atSempahore[ptGraphics->uCurrentFrameIndex]}, + .auSignalSemaphoreValues = {ulValue2}, + }; + + if(!gptGfx->present(ptGraphics, &tCommandBuffer1, &tSubmitInfo1)) + gptGfx->resize(ptGraphics); + + pl_end_profile_frame(); +} + +//----------------------------------------------------------------------------- +// [SECTION] helper function declarations +//----------------------------------------------------------------------------- + +static inline float +wrap_angle(float tTheta) +{ + static const float f2Pi = 2.0f * PL_PI; + const float fMod = fmodf(tTheta, f2Pi); + if (fMod > PL_PI) return fMod - f2Pi; + else if (fMod < -PL_PI) return fMod + f2Pi; + return fMod; +} + +void +camera_translate(plCamera* ptCamera, float fDx, float fDy, float fDz) +{ + ptCamera->tPos = pl_add_vec3(ptCamera->tPos, pl_mul_vec3_scalarf(ptCamera->_tRightVec, fDx)); + ptCamera->tPos = pl_add_vec3(ptCamera->tPos, pl_mul_vec3_scalarf(ptCamera->_tForwardVec, fDz)); + ptCamera->tPos.y += fDy; +} + +void +camera_rotate(plCamera* ptCamera, float fDPitch, float fDYaw) +{ + ptCamera->fPitch += fDPitch; + ptCamera->fYaw += fDYaw; + + ptCamera->fYaw = wrap_angle(ptCamera->fYaw); + ptCamera->fPitch = pl_clampf(0.995f * -PL_PI_2, ptCamera->fPitch, 0.995f * PL_PI_2); +} + +void +camera_update(plCamera* ptCamera) +{ + //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~update view~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + // world space + static const plVec4 tOriginalUpVec = {0.0f, 1.0f, 0.0f, 0.0f}; + static const plVec4 tOriginalForwardVec = {0.0f, 0.0f, 1.0f, 0.0f}; + static const plVec4 tOriginalRightVec = {-1.0f, 0.0f, 0.0f, 0.0f}; + + const plMat4 tXRotMat = pl_mat4_rotate_vec3(ptCamera->fPitch, tOriginalRightVec.xyz); + const plMat4 tYRotMat = pl_mat4_rotate_vec3(ptCamera->fYaw, tOriginalUpVec.xyz); + const plMat4 tZRotMat = pl_mat4_rotate_vec3(ptCamera->fRoll, tOriginalForwardVec.xyz); + const plMat4 tTranslate = pl_mat4_translate_vec3((plVec3){ptCamera->tPos.x, ptCamera->tPos.y, ptCamera->tPos.z}); + + // rotations: rotY * rotX * rotZ + plMat4 tRotations = pl_mul_mat4t(&tXRotMat, &tZRotMat); + tRotations = pl_mul_mat4t(&tYRotMat, &tRotations); + + // update camera vectors + ptCamera->_tRightVec = pl_norm_vec4(pl_mul_mat4_vec4(&tRotations, tOriginalRightVec)).xyz; + ptCamera->_tUpVec = pl_norm_vec4(pl_mul_mat4_vec4(&tRotations, tOriginalUpVec)).xyz; + ptCamera->_tForwardVec = pl_norm_vec4(pl_mul_mat4_vec4(&tRotations, tOriginalForwardVec)).xyz; + + // update camera transform: translate * rotate + ptCamera->tTransformMat = pl_mul_mat4t(&tTranslate, &tRotations); + + // update camera view matrix + ptCamera->tViewMat = pl_mat4t_invert(&ptCamera->tTransformMat); + + // flip x & y so camera looks down +z and remains right handed (+x to the right) + const plMat4 tFlipXY = pl_mat4_scale_xyz(-1.0f, -1.0f, 1.0f); + ptCamera->tViewMat = pl_mul_mat4t(&tFlipXY, &ptCamera->tViewMat); + + //~~~~~~~~~~~~~~~~~~~~~~~~~~~update projection~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + const float fInvtanHalfFovy = 1.0f / tanf(ptCamera->fFieldOfView / 2.0f); + ptCamera->tProjMat.col[0].x = fInvtanHalfFovy / ptCamera->fAspectRatio; + ptCamera->tProjMat.col[1].y = fInvtanHalfFovy; + ptCamera->tProjMat.col[2].z = ptCamera->fFarZ / (ptCamera->fFarZ - ptCamera->fNearZ); + ptCamera->tProjMat.col[2].w = 1.0f; + ptCamera->tProjMat.col[3].z = -ptCamera->fNearZ * ptCamera->fFarZ / (ptCamera->fFarZ - ptCamera->fNearZ); + ptCamera->tProjMat.col[3].w = 0.0f; +} + +void +resize_offscreen_resources(plAppData* ptAppData) +{ + plGraphics* ptGraphics = &ptAppData->tGraphics; + plDevice* ptDevice = &ptGraphics->tDevice; + + plIO* ptIO = gptIO->get_io(); + ptAppData->tCamera.fAspectRatio = ptIO->afMainViewportSize[0] / ptIO->afMainViewportSize[1]; + + const plTextureDesc tColorTextureDesc = { + .tDimensions = {ptAppData->tOffscreenSize.x, ptAppData->tOffscreenSize.y, 1}, + .tFormat = PL_FORMAT_R32G32B32A32_FLOAT, + .uLayers = 1, + .uMips = 1, + .tType = PL_TEXTURE_TYPE_2D, + .tUsage = PL_TEXTURE_USAGE_SAMPLED | PL_TEXTURE_USAGE_COLOR_ATTACHMENT, + .tInitialUsage = PL_TEXTURE_USAGE_SAMPLED + }; + + const plTextureDesc tDepthTextureDesc = { + .tDimensions = {ptAppData->tOffscreenSize.x, ptAppData->tOffscreenSize.y, 1}, + .tFormat = PL_FORMAT_D32_FLOAT_S8_UINT, + .uLayers = 1, + .uMips = 1, + .tType = PL_TEXTURE_TYPE_2D, + .tUsage = PL_TEXTURE_USAGE_DEPTH_STENCIL_ATTACHMENT, + .tInitialUsage = PL_TEXTURE_USAGE_DEPTH_STENCIL_ATTACHMENT + }; + + plRenderPassAttachments atAttachmentSets[PL_FRAMES_IN_FLIGHT] = {0}; + + for(uint32_t i = 0; i < PL_FRAMES_IN_FLIGHT; i++) + { + // queue old resources for deletion + gptDevice->queue_texture_for_deletion(ptDevice, ptAppData->atColorTexture[i]); + gptDevice->queue_texture_for_deletion(ptDevice, ptAppData->atDepthTexture[i]); + + // create new textures + ptAppData->atColorTexture[i] = gptDevice->create_texture(ptDevice, &tColorTextureDesc, "color texture"); + ptAppData->atDepthTexture[i] = gptDevice->create_texture(ptDevice, &tDepthTextureDesc, "depth texture"); + + // retrieve textures + plTexture* ptColorTexture = gptDevice->get_texture(ptDevice, ptAppData->atColorTexture[i]); + plTexture* ptDepthTexture = gptDevice->get_texture(ptDevice, ptAppData->atDepthTexture[i]); + + plDeviceMemoryAllocatorI* ptAllocator = gptGpuAllocators->get_local_dedicated_allocator(ptDevice); + + // allocate memory + const plDeviceMemoryAllocation tColorAllocation = ptAllocator->allocate(ptAllocator->ptInst, + ptColorTexture->tMemoryRequirements.uMemoryTypeBits, + ptColorTexture->tMemoryRequirements.ulSize, + ptColorTexture->tMemoryRequirements.ulAlignment, + "color texture memory"); + + const plDeviceMemoryAllocation tDepthAllocation = ptAllocator->allocate(ptAllocator->ptInst, + ptDepthTexture->tMemoryRequirements.uMemoryTypeBits, + ptDepthTexture->tMemoryRequirements.ulSize, + ptDepthTexture->tMemoryRequirements.ulAlignment, + "depth texture memory"); + + // bind memory + gptDevice->bind_texture_to_memory(ptDevice, ptAppData->atColorTexture[i], &tColorAllocation); + gptDevice->bind_texture_to_memory(ptDevice, ptAppData->atDepthTexture[i], &tDepthAllocation); + + // add textures to attachment set for render pass + atAttachmentSets[i].atViewAttachments[0] = ptAppData->atDepthTexture[i]; + atAttachmentSets[i].atViewAttachments[1] = ptAppData->atColorTexture[i]; + } + gptDevice->update_render_pass_attachments(ptDevice, ptAppData->tOffscreenRenderPass, ptAppData->tOffscreenSize, atAttachmentSets); +} \ No newline at end of file diff --git a/extensions/pl_debug_ext.c b/extensions/pl_debug_ext.c index 0738a253..f2d6d267 100644 --- a/extensions/pl_debug_ext.c +++ b/extensions/pl_debug_ext.c @@ -23,7 +23,6 @@ Index of this file: #include "pl_debug_ext.h" #define PL_MATH_INCLUDE_FUNCTIONS #include "pl_math.h" -#include "pl_ui.h" // extra #include "pl_profile.h" @@ -31,8 +30,8 @@ Index of this file: #include "pl_log.h" // extensions -#include "pl_ui.h" -#include "pl_ui_internal.h" +#include "pl_draw_ext.h" +#include "pl_ui_ext.h" #include "pl_stats_ext.h" #include "pl_graphics_ext.h" #include "pl_gpu_allocators_ext.h" @@ -46,6 +45,9 @@ static const plApiRegistryI* gptApiRegistry = NULL; static const plStatsI* ptStatsApi = NULL; static const plDataRegistryI* ptDataRegistry = NULL; static const plGPUAllocatorsI* gptGpuAllocators = NULL; +static const plUiI* gptUI = NULL; +static const plIOI* gptIO = NULL; +static const plDrawI* gptDraw = NULL; // contexts static plMemoryContext* ptMemoryCtx = NULL; @@ -149,70 +151,70 @@ pl__show_memory_allocations(bool* bValue) if(!ptDevice) ptDevice = ptDataRegistry->get_data("device"); - if(pl_begin_window("Memory Allocations", bValue, false)) + if(gptUI->begin_window("Memory Allocations", bValue, false)) { - pl_layout_dynamic(0.0f, 1); + gptUI->layout_dynamic(0.0f, 1); if(ptMemoryCtx->szMemoryUsage > 1000000000) - pl_text("General Memory Usage: %0.3f gb", (double)ptMemoryCtx->szMemoryUsage / 1000000000); + gptUI->text("General Memory Usage: %0.3f gb", (double)ptMemoryCtx->szMemoryUsage / 1000000000); else if(ptMemoryCtx->szMemoryUsage > 1000000) - pl_text("General Memory Usage: %0.3f mb", (double)ptMemoryCtx->szMemoryUsage / 1000000); + gptUI->text("General Memory Usage: %0.3f mb", (double)ptMemoryCtx->szMemoryUsage / 1000000); else if(ptMemoryCtx->szMemoryUsage > 1000) - pl_text("General Memory Usage: %0.3f kb", (double)ptMemoryCtx->szMemoryUsage / 1000); + gptUI->text("General Memory Usage: %0.3f kb", (double)ptMemoryCtx->szMemoryUsage / 1000); else - pl_text("General Memory Usage: %llu bytes", (double)ptMemoryCtx->szMemoryUsage); + gptUI->text("General Memory Usage: %llu bytes", (double)ptMemoryCtx->szMemoryUsage); if(ptDevice->ptGraphics->szHostMemoryInUse > 1000000000) - pl_text("Host Graphics Memory Usage: %0.3f gb", (double)ptDevice->ptGraphics->szHostMemoryInUse / 1000000000); + gptUI->text("Host Graphics Memory Usage: %0.3f gb", (double)ptDevice->ptGraphics->szHostMemoryInUse / 1000000000); else if(ptDevice->ptGraphics->szHostMemoryInUse > 1000000) - pl_text("Host Graphics Memory Usage: %0.3f mb", (double)ptDevice->ptGraphics->szHostMemoryInUse / 1000000); + gptUI->text("Host Graphics Memory Usage: %0.3f mb", (double)ptDevice->ptGraphics->szHostMemoryInUse / 1000000); else if(ptDevice->ptGraphics->szHostMemoryInUse > 1000) - pl_text("Host Graphics Memory Usage: %0.3f kb", (double)ptDevice->ptGraphics->szHostMemoryInUse / 1000); + gptUI->text("Host Graphics Memory Usage: %0.3f kb", (double)ptDevice->ptGraphics->szHostMemoryInUse / 1000); else - pl_text("Host Graphics Memory Usage: %llu bytes", (double)ptDevice->ptGraphics->szHostMemoryInUse); + gptUI->text("Host Graphics Memory Usage: %llu bytes", (double)ptDevice->ptGraphics->szHostMemoryInUse); - pl_text("Active Allocations: %u", ptMemoryCtx->szActiveAllocations); - pl_text("Freed Allocations: %u", ptMemoryCtx->szAllocationFrees); + gptUI->text("Active Allocations: %u", ptMemoryCtx->szActiveAllocations); + gptUI->text("Freed Allocations: %u", ptMemoryCtx->szAllocationFrees); static char pcFile[1024] = {0}; - pl_layout_template_begin(30.0f); - pl_layout_template_push_static(50.0f); - pl_layout_template_push_variable(300.0f); - pl_layout_template_push_variable(50.0f); - pl_layout_template_push_variable(50.0f); - pl_layout_template_end(); + gptUI->layout_template_begin(30.0f); + gptUI->layout_template_push_static(50.0f); + gptUI->layout_template_push_variable(300.0f); + gptUI->layout_template_push_variable(50.0f); + gptUI->layout_template_push_variable(50.0f); + gptUI->layout_template_end(); - pl_text("%s", "Entry"); - pl_text("%s", "File"); - pl_text("%s", "Line"); - pl_text("%s", "Size"); + gptUI->text("%s", "Entry"); + gptUI->text("%s", "File"); + gptUI->text("%s", "Line"); + gptUI->text("%s", "Size"); - pl_layout_dynamic(0.0f, 1); - pl_separator(); + gptUI->layout_dynamic(0.0f, 1); + gptUI->separator(); - pl_layout_template_begin(30.0f); - pl_layout_template_push_static(50.0f); - pl_layout_template_push_variable(300.0f); - pl_layout_template_push_variable(50.0f); - pl_layout_template_push_variable(50.0f); - pl_layout_template_end(); + gptUI->layout_template_begin(30.0f); + gptUI->layout_template_push_static(50.0f); + gptUI->layout_template_push_variable(300.0f); + gptUI->layout_template_push_variable(50.0f); + gptUI->layout_template_push_variable(50.0f); + gptUI->layout_template_end(); const uint32_t uOriginalAllocationCount = pl_sb_size(ptMemoryCtx->sbtAllocations); plUiClipper tClipper = {uOriginalAllocationCount}; - while(pl_step_clipper(&tClipper)) + while(gptUI->step_clipper(&tClipper)) { for(uint32_t i = tClipper.uDisplayStart; i < tClipper.uDisplayEnd; i++) { plAllocationEntry tEntry = ptMemoryCtx->sbtAllocations[i]; strncpy(pcFile, tEntry.pcFile, 1024); - pl_text("%i", i); - pl_text("%s", pcFile); - pl_text("%i", tEntry.iLine); - pl_text("%u", tEntry.szSize); + gptUI->text("%i", i); + gptUI->text("%s", pcFile); + gptUI->text("%i", tEntry.iLine); + gptUI->text("%u", tEntry.szSize); } } - pl_end_window(); + gptUI->end_window(); } } @@ -230,10 +232,10 @@ pl__show_profiling(bool* bValue) static bool bFirstRun = true; - if(pl_begin_window("Profiling (WIP)", bValue, false)) + if(gptUI->begin_window("Profiling (WIP)", bValue, false)) { - const plVec2 tWindowSize = pl_get_window_size(); - const plVec2 tWindowPos = pl_get_window_pos(); + const plVec2 tWindowSize = gptUI->get_window_size(); + const plVec2 tWindowPos = gptUI->get_window_pos(); const plVec2 tWindowEnd = pl_add_vec2(tWindowSize, tWindowPos); plProfileSample* ptSamples = NULL; @@ -259,10 +261,10 @@ pl__show_profiling(bool* bValue) fDeltaTime = ptIOCtx->fDeltaTime; } - pl_layout_static(0.0f, 100.0f, 2); + gptUI->layout_static(0.0f, 100.0f, 2); if(pl_sb_size(sbtSamples) == 0) { - if(pl_button("Capture Frame")) + if(gptUI->button("Capture Frame")) { pl_sb_resize(sbtSamples, uSampleSize); memcpy(sbtSamples, ptSamples, sizeof(plProfileSample) * uSampleSize); @@ -270,76 +272,73 @@ pl__show_profiling(bool* bValue) } else { - if(pl_button("Release Frame")) + if(gptUI->button("Release Frame")) { pl_sb_reset(sbtSamples); } } - pl_text("Frame Time: %0.3f", fDeltaTime); + gptUI->text("Frame Time: %0.3f", fDeltaTime); - pl_layout_dynamic(0.0f, 1); + gptUI->layout_dynamic(0.0f, 1); - pl_separator(); + gptUI->separator(); - if(pl_begin_tab_bar("profiling tabs")) + if(gptUI->begin_tab_bar("profiling tabs")) { - if(pl_begin_tab("Table")) + if(gptUI->begin_tab("Table")) { - pl_layout_template_begin(0.0f); - pl_layout_template_push_variable(300.0f); - pl_layout_template_push_variable(50.0f); - pl_layout_template_push_variable(50.0f); - pl_layout_template_push_variable(100.0f); - pl_layout_template_end(); - - pl_text("Sample Name"); - pl_text("Time (ms)"); - pl_text("Start (ms)"); - pl_text("Frame Time"); - - pl_layout_dynamic(0.0f, 1); - pl_separator(); - - pl_layout_template_begin(0.0f); - pl_layout_template_push_variable(300.0f); - pl_layout_template_push_variable(50.0f); - pl_layout_template_push_variable(50.0f); - pl_layout_template_push_variable(100.0f); - pl_layout_template_end(); + gptUI->layout_template_begin(0.0f); + gptUI->layout_template_push_variable(300.0f); + gptUI->layout_template_push_variable(50.0f); + gptUI->layout_template_push_variable(50.0f); + gptUI->layout_template_push_variable(100.0f); + gptUI->layout_template_end(); + + gptUI->text("Sample Name"); + gptUI->text("Time (ms)"); + gptUI->text("Start (ms)"); + gptUI->text("Frame Time"); + + gptUI->layout_dynamic(0.0f, 1); + gptUI->separator(); + + gptUI->layout_template_begin(0.0f); + gptUI->layout_template_push_variable(300.0f); + gptUI->layout_template_push_variable(50.0f); + gptUI->layout_template_push_variable(50.0f); + gptUI->layout_template_push_variable(100.0f); + gptUI->layout_template_end(); plUiClipper tClipper = {uSampleSize}; - const plVec4 tOriginalProgressColor = pl_get_context()->tColorScheme.tProgressBarCol; - plVec4* tTempProgressColor = &pl_get_context()->tColorScheme.tProgressBarCol; - while(pl_step_clipper(&tClipper)) + while(gptUI->step_clipper(&tClipper)) { for(uint32_t i = tClipper.uDisplayStart; i < tClipper.uDisplayEnd; i++) { - pl_indent(15.0f * (float)(ptSamples[i].uDepth + 1)); - pl_color_text(atColors[ptSamples[i].uDepth % 6], "%s", ptSamples[i].pcName); - pl_unindent(15.0f * (float)(ptSamples[i].uDepth + 1)); - pl_text("%7.3f", ptSamples[i].dDuration * 1000.0); - pl_text("%7.3f", ptSamples[i].dStartTime * 1000.0); - *tTempProgressColor = atColors[ptSamples[i].uDepth % 6]; - pl_progress_bar((float)(ptSamples[i].dDuration / (double)fDeltaTime), (plVec2){-1.0f, 0.0f}, NULL); + gptUI->indent(15.0f * (float)(ptSamples[i].uDepth + 1)); + gptUI->color_text(atColors[ptSamples[i].uDepth % 6], "%s", ptSamples[i].pcName); + gptUI->unindent(15.0f * (float)(ptSamples[i].uDepth + 1)); + gptUI->text("%7.3f", ptSamples[i].dDuration * 1000.0); + gptUI->text("%7.3f", ptSamples[i].dStartTime * 1000.0); + gptUI->push_theme_color(PL_UI_COLOR_PROGRESS_BAR, &atColors[ptSamples[i].uDepth % 6]); + gptUI->progress_bar((float)(ptSamples[i].dDuration / (double)fDeltaTime), (plVec2){-1.0f, 0.0f}, NULL); + gptUI->pop_theme_color(1); } } - *tTempProgressColor = tOriginalProgressColor; - - pl_end_tab(); + gptUI->end_tab(); } - if(pl_begin_tab("Graph")) + if(gptUI->begin_tab("Graph")) { - const plVec2 tParentCursorPos = pl_get_cursor_pos(); - pl_layout_dynamic(tWindowEnd.y - tParentCursorPos.y - 5.0f, 1); - if(pl_begin_child("timeline")) + const plVec2 tParentCursorPos = gptUI->get_cursor_pos(); + gptUI->layout_dynamic(tWindowEnd.y - tParentCursorPos.y - 5.0f, 1); + if(gptUI->begin_child("timeline")) { - const plVec2 tChildWindowSize = pl_get_window_size(); - const plVec2 tCursorPos = pl_get_cursor_pos(); - pl_layout_space_begin(PL_UI_LAYOUT_ROW_TYPE_STATIC, pl_get_window_size().y - 50.0f, uSampleSize + 1); + const plVec2 tChildWindowSize = gptUI->get_window_size(); + const plVec2 tCursorPos = gptUI->get_cursor_pos(); + gptUI->layout_space_begin(PL_UI_LAYOUT_ROW_TYPE_STATIC, gptUI->get_window_size().y - 50.0f, uSampleSize + 1); (void)tWindowSize; static double dInitialVisibleTime = 0.016; @@ -362,22 +361,22 @@ pl__show_profiling(bool* bValue) const double dConvertToTime = dVisibleTime / tChildWindowSize.x; // timeline bar - plDrawLayer* ptFgLayer = pl_get_window_fg_drawlayer(); + plDrawLayer2D* ptFgLayer = gptUI->get_window_fg_drawlayer(); - pl_layout_space_push(0.0f, 0.0f, (float)(dMaxTime * dConvertToPixel), 50.0f); + gptUI->layout_space_push(0.0f, 0.0f, (float)(dMaxTime * dConvertToPixel), 50.0f); const plVec2 tTimelineSize = {(float)(dMaxTime * dConvertToPixel), tWindowEnd.y - tParentCursorPos.y - 15.0f}; const plVec2 tTimelineBarSize = {(float)(dMaxTime * dConvertToPixel), 50.0f}; // pl_invisible_button("hitregion", tTimelineSize); // bool bHovered = pl_was_last_item_hovered(); - const plVec2 tMousePos = pl_get_mouse_pos(); + const plVec2 tMousePos = gptIO->get_mouse_pos(); const plRect tWidgetRect = pl_calculate_rect(tCursorPos, tTimelineSize); bool bHovered = pl_rect_contains_point(&tWidgetRect, tMousePos); if(bHovered) { const double dStartVisibleTime = dInitialVisibleTime; - float fWheel = pl_get_mouse_wheel(); + float fWheel = gptIO->get_mouse_wheel(); if(fWheel < 0) dInitialVisibleTime += dInitialVisibleTime * 0.2; else if(fWheel > 0) dInitialVisibleTime -= dInitialVisibleTime * 0.2; dInitialVisibleTime = pl_clampd(0.0001, dInitialVisibleTime, fDeltaTime); @@ -387,30 +386,30 @@ pl__show_profiling(bool* bValue) const double dNewConvertToPixel = tChildWindowSize.x / dInitialVisibleTime; const double dNewConvertToTime = dInitialVisibleTime / tChildWindowSize.x; - const double dTimeHovered = (double)dConvertToTime * (double)(tMousePos.x - tParentCursorPos.x + pl_get_window_scroll().x); + const double dTimeHovered = (double)dConvertToTime * (double)(tMousePos.x - tParentCursorPos.x + gptUI->get_window_scroll().x); const float fConservedRatio = (tMousePos.x - tParentCursorPos.x) / tChildWindowSize.x; const double dOldPixelStart = dConvertToPixel * dTimeHovered; const double dNewPixelStart = dNewConvertToPixel * (dTimeHovered - fConservedRatio * dInitialVisibleTime); - pl_set_window_scroll((plVec2){(float)dNewPixelStart, 0.0f}); + gptUI->set_window_scroll((plVec2){(float)dNewPixelStart, 0.0f}); } - if(pl_is_mouse_dragging(PL_MOUSE_BUTTON_LEFT, 5.0f)) + if(gptIO->is_mouse_dragging(PL_MOUSE_BUTTON_LEFT, 5.0f)) { - const plVec2 tWindowScroll = pl_get_window_scroll(); - const plVec2 tMouseDrag = pl_get_mouse_drag_delta(PL_MOUSE_BUTTON_LEFT, 5.0f); - pl_set_window_scroll((plVec2){tWindowScroll.x - tMouseDrag.x, tWindowScroll.y}); - pl_reset_mouse_drag_delta(PL_MOUSE_BUTTON_LEFT); + const plVec2 tWindowScroll = gptUI->get_window_scroll(); + const plVec2 tMouseDrag = gptIO->get_mouse_drag_delta(PL_MOUSE_BUTTON_LEFT, 5.0f); + gptUI->set_window_scroll((plVec2){tWindowScroll.x - tMouseDrag.x, tWindowScroll.y}); + gptIO->reset_mouse_drag_delta(PL_MOUSE_BUTTON_LEFT); } } - pl_add_rect_filled(ptFgLayer, tCursorPos, pl_add_vec2(tCursorPos, tTimelineBarSize), (plVec4){0.5f, 0.0f, 0.0f, 0.7f}); + gptDraw->add_rect_filled(ptFgLayer, tCursorPos, pl_add_vec2(tCursorPos, tTimelineBarSize), (plVec4){0.5f, 0.0f, 0.0f, 0.7f}); const double dUnitMultiplier = 1000.0; uint32_t uDecimalPlaces = 0; // major ticks - const float fScrollStartPosPixel = pl_get_window_scroll().x; - const float fScrollEndPosPixel = fScrollStartPosPixel + pl_get_window_size().x; + const float fScrollStartPosPixel = gptUI->get_window_scroll().x; + const float fScrollEndPosPixel = fScrollStartPosPixel + gptUI->get_window_size().x; const double dScrollStartPosTime = dConvertToTime * (double)fScrollStartPosPixel; const double dScrollEndPosTime = dConvertToTime * (double)fScrollEndPosPixel; const uint32_t uScrollStartPosNearestUnit = (uint32_t)round(dScrollStartPosTime / dIncrement); @@ -440,12 +439,12 @@ pl__show_profiling(bool* bValue) const double dLineX0 = (double)(dTime0 * dConvertToPixel) + tCursorPos.x; char* pcDecimals = pl_temp_allocator_sprintf(&tTempAllocator, " %%0.%uf ms ", uDecimalPlaces); char* pcText0 = pl_temp_allocator_sprintf(&tTempAllocator, pcDecimals, (double)dTime0 * dUnitMultiplier); - const plRect tBB0 = pl_calculate_text_bb(pl_get_default_font(), 13.0f, (plVec2){roundf((float)dLineX0), tCursorPos.y + 20.0f}, pcText0, 0.0f); + const plRect tBB0 = gptDraw->calculate_text_bb(gptUI->get_default_font(), 13.0f, (plVec2){roundf((float)dLineX0), tCursorPos.y + 20.0f}, pcText0, 0.0f); const double dTime1 = dTime0 + dIncrement; const float dLineX1 = (float)(dTime1 * dConvertToPixel) + tCursorPos.x; char* pcText1 = pl_temp_allocator_sprintf(&tTempAllocator, pcDecimals, (double)dTime1 * dUnitMultiplier); - const plRect tBB1 = pl_calculate_text_bb(pl_get_default_font(), 13.0f, (plVec2){roundf((float)dLineX1), tCursorPos.y + 20.0f}, pcText1, 0.0f); + const plRect tBB1 = gptDraw->calculate_text_bb(gptUI->get_default_font(), 13.0f, (plVec2){roundf((float)dLineX1), tCursorPos.y + 20.0f}, pcText1, 0.0f); pl_temp_allocator_reset(&tTempAllocator); if(!pl_rect_overlaps_rect(&tBB0, &tBB1)) @@ -464,7 +463,7 @@ pl__show_profiling(bool* bValue) while(dCurrentTime < dEndTime) { const float fLineX = (float)((dCurrentTime * dConvertToPixel)) + tCursorPos.x; - pl_add_line(ptFgLayer, (plVec2){fLineX, tCursorPos.y}, (plVec2){fLineX, tCursorPos.y + 10.0f}, (plVec4){1.0f, 1.0f, 1.0f, 1.0f}, 1.0f); + gptDraw->add_line(ptFgLayer, (plVec2){fLineX, tCursorPos.y}, (plVec2){fLineX, tCursorPos.y + 10.0f}, (plVec4){1.0f, 1.0f, 1.0f, 1.0f}, 1.0f); dCurrentTime += dIncrement * 0.5f; } @@ -473,7 +472,7 @@ pl__show_profiling(bool* bValue) while(dCurrentTime < dEndTime) { const float fLineX = (float)((dCurrentTime * dConvertToPixel)) + tCursorPos.x; - pl_add_line(ptFgLayer, (plVec2){fLineX, tCursorPos.y}, (plVec2){fLineX, tCursorPos.y + 5.0f}, (plVec4){1.0f, 1.0f, 1.0f, 1.0f}, 1.0f); + gptDraw->add_line(ptFgLayer, (plVec2){fLineX, tCursorPos.y}, (plVec2){fLineX, tCursorPos.y + 5.0f}, (plVec4){1.0f, 1.0f, 1.0f, 1.0f}, 1.0f); dCurrentTime += dIncrement * 0.1f; } @@ -484,61 +483,59 @@ pl__show_profiling(bool* bValue) const float fLineX = (float)((dCurrentTime * dConvertToPixel)) + tCursorPos.x; char* pcDecimals = pl_temp_allocator_sprintf(&tTempAllocator, "%%0.%uf ms", uDecimalPlaces); char* pcText = pl_temp_allocator_sprintf(&tTempAllocator, pcDecimals, (double)dCurrentTime * dUnitMultiplier); - const float fTextWidth = pl_calculate_text_size(pl_get_default_font(), 13.0f, pcText, 0.0f).x; - pl_add_line(ptFgLayer, (plVec2){fLineX, tCursorPos.y}, (plVec2){fLineX, tCursorPos.y + 20.0f}, (plVec4){1.0f, 1.0f, 1.0f, 1.0f}, 1.0f); - pl_add_text(ptFgLayer, pl_get_default_font(), 13.0f, (plVec2){roundf(fLineX - fTextWidth / 2.0f), tCursorPos.y + 20.0f}, (plVec4){1.0f, 1.0f, 1.0f, 1.0f}, pcText, 0.0f); + const float fTextWidth = gptDraw->calculate_text_size(gptUI->get_default_font(), 13.0f, pcText, 0.0f).x; + gptDraw->add_line(ptFgLayer, (plVec2){fLineX, tCursorPos.y}, (plVec2){fLineX, tCursorPos.y + 20.0f}, (plVec4){1.0f, 1.0f, 1.0f, 1.0f}, 1.0f); + gptDraw->add_text(ptFgLayer, gptUI->get_default_font(), 13.0f, (plVec2){roundf(fLineX - fTextWidth / 2.0f), tCursorPos.y + 20.0f}, (plVec4){1.0f, 1.0f, 1.0f, 1.0f}, pcText, 0.0f); pl_temp_allocator_reset(&tTempAllocator); dCurrentTime += dIncrement; } - const plVec4 tOriginalButtonColor = pl_get_context()->tColorScheme.tButtonCol; - plVec4* tTempButtonColor = &pl_get_context()->tColorScheme.tButtonCol; for(uint32_t i = 0; i < uSampleSize; i++) { const float fPixelWidth = (float)(dConvertToPixel * ptSamples[i].dDuration); const float fPixelStart = (float)(dConvertToPixel * ptSamples[i].dStartTime); - pl_layout_space_push(fPixelStart, (float)ptSamples[i].uDepth * 25.0f + 55.0f, fPixelWidth, 20.0f); + gptUI->layout_space_push(fPixelStart, (float)ptSamples[i].uDepth * 25.0f + 55.0f, fPixelWidth, 20.0f); char* pcTempBuffer = pl_temp_allocator_sprintf(&tTempAllocator, "%s##pro%u", ptSamples[i].pcName, i); - *tTempButtonColor = atColors[ptSamples[i].uDepth % 6]; - if(pl_button(pcTempBuffer)) + gptUI->push_theme_color(PL_UI_COLOR_BUTTON, &atColors[ptSamples[i].uDepth % 6]); + if(gptUI->button(pcTempBuffer)) { dInitialVisibleTime = pl_clampd(0.0001, ptSamples[i].dDuration, (double)fDeltaTime); const double dNewConvertToPixel = tChildWindowSize.x / dInitialVisibleTime; const double dNewPixelStart = dNewConvertToPixel * (ptSamples[i].dStartTime + 0.5 * ptSamples[i].dDuration); const double dNewScrollX = dNewPixelStart - dNewConvertToPixel * dInitialVisibleTime * 0.5; - pl_set_window_scroll((plVec2){(float)dNewScrollX, 0.0f}); + gptUI->set_window_scroll((plVec2){(float)dNewScrollX, 0.0f}); } pl_temp_allocator_reset(&tTempAllocator); - if(pl_was_last_item_hovered()) + if(gptUI->was_last_item_hovered()) { // bHovered = false; - pl_begin_tooltip(); - pl_color_text(atColors[ptSamples[i].uDepth % 6], "%s", ptSamples[i].pcName); - pl_text("Duration: %0.7f seconds", ptSamples[i].dDuration); - pl_text("Start Time: %0.7f seconds", ptSamples[i].dStartTime); - pl_color_text(atColors[ptSamples[i].uDepth % 6], "Frame Time: %0.2f %%", 100.0 * ptSamples[i].dDuration / (double)fDeltaTime); - pl_end_tooltip(); + gptUI->begin_tooltip(); + gptUI->color_text(atColors[ptSamples[i].uDepth % 6], "%s", ptSamples[i].pcName); + gptUI->text("Duration: %0.7f seconds", ptSamples[i].dDuration); + gptUI->text("Start Time: %0.7f seconds", ptSamples[i].dStartTime); + gptUI->color_text(atColors[ptSamples[i].uDepth % 6], "Frame Time: %0.2f %%", 100.0 * ptSamples[i].dDuration / (double)fDeltaTime); + gptUI->end_tooltip(); } + gptUI->pop_theme_color(1); } - *tTempButtonColor = tOriginalButtonColor; if(bHovered) { - pl_add_line(ptFgLayer, (plVec2){tMousePos.x, tCursorPos.y}, (plVec2){tMousePos.x, tWindowEnd.y}, (plVec4){1.0f, 1.0f, 1.0f, 1.0f}, 1.0f); - char* pcText = pl_temp_allocator_sprintf(&tTempAllocator, "%0.6f", (double)dConvertToTime * (double)(tMousePos.x - tParentCursorPos.x + pl_get_window_scroll().x)); - pl_add_text(ptFgLayer, pl_get_default_font(), 13.0f, tMousePos, (plVec4){1.0f, 1.0f, 1.0f, 1.0f}, pcText, 0.0f); + gptDraw->add_line(ptFgLayer, (plVec2){tMousePos.x, tCursorPos.y}, (plVec2){tMousePos.x, tWindowEnd.y}, (plVec4){1.0f, 1.0f, 1.0f, 1.0f}, 1.0f); + char* pcText = pl_temp_allocator_sprintf(&tTempAllocator, "%0.6f", (double)dConvertToTime * (double)(tMousePos.x - tParentCursorPos.x + gptUI->get_window_scroll().x)); + gptDraw->add_text(ptFgLayer, gptUI->get_default_font(), 13.0f, tMousePos, (plVec4){1.0f, 1.0f, 1.0f, 1.0f}, pcText, 0.0f); pl_temp_allocator_reset(&tTempAllocator); } - pl_layout_space_end(); + gptUI->layout_space_end(); - pl_end_child(); + gptUI->end_child(); } - pl_end_tab(); + gptUI->end_tab(); } - pl_end_tab_bar(); + gptUI->end_tab_bar(); } - pl_end_window(); + gptUI->end_window(); } } @@ -569,7 +566,7 @@ pl__show_statistics(bool* bValue) for(uint32_t i = 0; i < uNameCount; i++) { sbppdFrameValues[i] = ptStatsApi->get_counter_data(ppcNames[i]); - float fCurrentWidth = pl_calculate_text_size(pl_get_default_font(), 13.0f, ppcNames[i], 0.0f).x; + float fCurrentWidth = gptDraw->calculate_text_size(gptUI->get_default_font(), 13.0f, ppcNames[i], 0.0f).x; if(fCurrentWidth > fLegendWidth) fLegendWidth = fCurrentWidth; } @@ -577,33 +574,33 @@ pl__show_statistics(bool* bValue) fLegendWidth += 5.0f; } - if(pl_begin_window("Statistics", bValue, false)) + if(gptUI->begin_window("Statistics", bValue, false)) { static bool bAllowNegative = false; - pl_text("Frame rate: %.0f FPS", ptIOCtx->fFrameRate); - pl_text("Frame time: %.6f s", ptIOCtx->fDeltaTime); - const plVec2 tCursor = pl_get_cursor_pos(); + gptUI->text("Frame rate: %.0f FPS", ptIOCtx->fFrameRate); + gptUI->text("Frame time: %.6f s", ptIOCtx->fDeltaTime); + const plVec2 tCursor = gptUI->get_cursor_pos(); - plDrawLayer* ptFgLayer = pl_get_window_fg_drawlayer(); - const plVec2 tWindowPos = pl_get_window_pos(); - const plVec2 tWindowSize = pl_sub_vec2(pl_get_window_size(), pl_sub_vec2(tCursor, tWindowPos)); + plDrawLayer2D* ptFgLayer = gptUI->get_window_fg_drawlayer(); + const plVec2 tWindowPos = gptUI->get_window_pos(); + const plVec2 tWindowSize = pl_sub_vec2(gptUI->get_window_size(), pl_sub_vec2(tCursor, tWindowPos)); - const plVec2 tWindowEnd = pl_add_vec2(pl_get_window_size(), tWindowPos); + const plVec2 tWindowEnd = pl_add_vec2(gptUI->get_window_size(), tWindowPos); - pl_layout_template_begin(tWindowSize.y - 15.0f); - pl_layout_template_push_static(fLegendWidth * 2.0f); - pl_layout_template_push_dynamic(); - pl_layout_template_end(); + gptUI->layout_template_begin(tWindowSize.y - 15.0f); + gptUI->layout_template_push_static(fLegendWidth * 2.0f); + gptUI->layout_template_push_dynamic(); + gptUI->layout_template_end(); - if(pl_begin_child("left")) + if(gptUI->begin_child("left")) { - pl_layout_dynamic(0.0f, 1); + gptUI->layout_dynamic(0.0f, 1); - const plVec4 tOriginalButtonColor = pl_get_context()->tColorScheme.tHeaderCol; - pl_get_context()->tColorScheme.tHeaderCol = (plVec4){0.0f, 0.5f, 0.0f, 0.75f}; + const plVec4 tNewHeaderColor = (plVec4){0.0f, 0.5f, 0.0f, 0.75f}; + gptUI->push_theme_color(PL_UI_COLOR_HEADER, &tNewHeaderColor); for(uint32_t i = 0; i < pl_sb_size(sbppdValues); i++) { - if(pl_selectable(ppcNames[i], &sbbValues[i])) + if(gptUI->selectable(ppcNames[i], &sbbValues[i])) { if(sbbValues[i]) { @@ -622,8 +619,8 @@ pl__show_statistics(bool* bValue) } } } - pl_get_context()->tColorScheme.tHeaderCol = tOriginalButtonColor; - pl_end_child(); + gptUI->pop_theme_color(1); + gptUI->end_child(); } uint32_t uSelectionSlot = 0; @@ -643,24 +640,24 @@ pl__show_statistics(bool* bValue) } - if(pl_begin_tab_bar("stat tabs")) + if(gptUI->begin_tab_bar("stat tabs")) { - if(pl_begin_tab("Plot")) + if(gptUI->begin_tab("Plot")) { - pl_layout_template_begin(tWindowSize.y - 30.0f); - pl_layout_template_push_variable(300.0f); - pl_layout_template_push_static(fLegendWidth * 2.0f + 10.0f); - pl_layout_template_end(); + gptUI->layout_template_begin(tWindowSize.y - 30.0f); + gptUI->layout_template_push_variable(300.0f); + gptUI->layout_template_push_static(fLegendWidth * 2.0f + 10.0f); + gptUI->layout_template_end(); - const plVec2 tCursor0 = pl_get_cursor_pos(); + const plVec2 tCursor0 = gptUI->get_cursor_pos(); const plVec2 tPlotSize = {tWindowEnd.x - tCursor0.x - 10.0f - fLegendWidth - 20.0f, tWindowSize.y - 40.0f}; const plVec2 tLegendSize = {fLegendWidth + 20.0f, tWindowSize.y - 40.0f}; - pl_invisible_button("plot", tPlotSize); + gptUI->invisible_button("plot", tPlotSize); - const plVec2 tCursor1 = pl_get_cursor_pos(); - pl_invisible_button("legend", tLegendSize); - pl_add_rect_filled(ptFgLayer, + const plVec2 tCursor1 = gptUI->get_cursor_pos(); + gptUI->invisible_button("legend", tLegendSize); + gptDraw->add_rect_filled(ptFgLayer, tCursor0, pl_add_vec2(tCursor0, tPlotSize), (plVec4){0.2f, 0.0f, 0.0f, 0.5f}); @@ -681,8 +678,8 @@ pl__show_statistics(bool* bValue) if(bAllowNegative) { - pl_add_text(ptFgLayer, pl_get_default_font(), 13.0f, (plVec2){roundf(tCursor0.x), roundf((float)dYCenter)}, (plVec4){1.0f, 1.0f, 1.0f, 1.0f}, "0", 0.0f); - pl_add_line(ptFgLayer, (plVec2){tCursor0.x, (float)dYCenter}, (plVec2){tCursor0.x + tPlotSize.x, (float)dYCenter}, (plVec4){1.0f, 1.0f, 1.0f, 1.0f}, 1.0f); + gptDraw->add_text(ptFgLayer, gptUI->get_default_font(), 13.0f, (plVec2){roundf(tCursor0.x), roundf((float)dYCenter)}, (plVec4){1.0f, 1.0f, 1.0f, 1.0f}, "0", 0.0f); + gptDraw->add_line(ptFgLayer, (plVec2){tCursor0.x, (float)dYCenter}, (plVec2){tCursor0.x + tPlotSize.x, (float)dYCenter}, (plVec4){1.0f, 1.0f, 1.0f, 1.0f}, 1.0f); } bAllowNegative = false; @@ -709,8 +706,8 @@ pl__show_statistics(bool* bValue) uint32_t uIndexStart = (uint32_t)ptIOCtx->ulFrameCount; const plVec2 tTextPoint = {tCursor1.x + 5.0f, tCursor1.y + i * 15.0f}; - pl_add_rect_filled(ptFgLayer, tTextPoint, (plVec2){tTextPoint.x + 13.0f, tTextPoint.y + 13.0f}, *ptColor); - pl_add_text(ptFgLayer, pl_get_default_font(), 13.0f, (plVec2){roundf(tTextPoint.x + 15.0f), roundf(tTextPoint.y)}, *ptColor, apcTempNames[i], 0.0f); + gptDraw->add_rect_filled(ptFgLayer, tTextPoint, (plVec2){tTextPoint.x + 13.0f, tTextPoint.y + 13.0f}, *ptColor); + gptDraw->add_text(ptFgLayer, gptUI->get_default_font(), 13.0f, (plVec2){roundf(tTextPoint.x + 15.0f), roundf(tTextPoint.y)}, *ptColor, apcTempNames[i], 0.0f); for(uint32_t j = 0; j < PL_STATS_MAX_FRAMES - 1; j++) { @@ -718,69 +715,69 @@ pl__show_statistics(bool* bValue) uint32_t uActualIndex1 = (uIndexStart + j + 1) % PL_STATS_MAX_FRAMES; const plVec2 tLineStart = {tCursor0.x + (float)(j * dXIncrement), (float)(dYCenter - dValues[uActualIndex0] * dConversion)}; const plVec2 tLineEnd = {tCursor0.x + (float)((j + 1) * dXIncrement), (float)(dYCenter - dValues[uActualIndex1] * dConversion)}; - pl_add_line(ptFgLayer, tLineStart, tLineEnd, *ptColor, 1.0f); + gptDraw->add_line(ptFgLayer, tLineStart, tLineEnd, *ptColor, 1.0f); if(j == PL_STATS_MAX_FRAMES - 2) { char acTextBuffer[32] = {0}; pl_sprintf(acTextBuffer, "%0.0f", dValues[uActualIndex1]); - pl_add_text(ptFgLayer, pl_get_default_font(), 13.0f, (plVec2){roundf(tLineEnd.x), roundf(tLineEnd.y) - 6.0f}, *ptColor, acTextBuffer, 0.0f); + gptDraw->add_text(ptFgLayer, gptUI->get_default_font(), 13.0f, (plVec2){roundf(tLineEnd.x), roundf(tLineEnd.y) - 6.0f}, *ptColor, acTextBuffer, 0.0f); } } } - pl_end_tab(); + gptUI->end_tab(); } - if(pl_begin_tab("Table")) + if(gptUI->begin_tab("Table")) { - pl_layout_template_begin(0.0f); - pl_layout_template_push_static(35.0f); + gptUI->layout_template_begin(0.0f); + gptUI->layout_template_push_static(35.0f); for(uint32_t i = 0; i < uSelectedCount; i++) - pl_layout_template_push_static(100.0f); - pl_layout_template_end(); + gptUI->layout_template_push_static(100.0f); + gptUI->layout_template_end(); - pl_text("Stat"); + gptUI->text("Stat"); for(uint32_t i = 0; i < uSelectedCount; i++) { - const float fXPos = pl_get_cursor_pos().x - 5.0f; - const float fYPos = pl_get_cursor_pos().y; - pl_add_line(ptFgLayer, (plVec2){fXPos, fYPos}, (plVec2){fXPos, 3000.0f}, (plVec4){0.7f, 0.0f, 0.0f, 1.0f}, 1.0f); - pl_button(apcTempNames[i]); + const float fXPos = gptUI->get_cursor_pos().x - 5.0f; + const float fYPos = gptUI->get_cursor_pos().y; + gptDraw->add_line(ptFgLayer, (plVec2){fXPos, fYPos}, (plVec2){fXPos, 3000.0f}, (plVec4){0.7f, 0.0f, 0.0f, 1.0f}, 1.0f); + gptUI->button(apcTempNames[i]); } - pl_layout_dynamic(0.0f, 1); - pl_separator(); + gptUI->layout_dynamic(0.0f, 1); + gptUI->separator(); - pl_layout_template_begin(0.0f); - pl_layout_template_push_static(35.0f); + gptUI->layout_template_begin(0.0f); + gptUI->layout_template_push_static(35.0f); for(uint32_t i = 0; i < uSelectedCount; i++) - pl_layout_template_push_static(100.0f); - pl_layout_template_end(); + gptUI->layout_template_push_static(100.0f); + gptUI->layout_template_end(); uint32_t uIndexStart = (uint32_t)ptIOCtx->ulFrameCount; plUiClipper tClipper = {PL_STATS_MAX_FRAMES}; - while(pl_step_clipper(&tClipper)) + while(gptUI->step_clipper(&tClipper)) { for(uint32_t i = tClipper.uDisplayStart; i < tClipper.uDisplayEnd; i++) { - pl_text("%u", i); + gptUI->text("%u", i); for(uint32_t j = 0; j < uSelectedCount; j++) { double* dValues = &sbdRawValues[j * PL_STATS_MAX_FRAMES]; uint32_t uActualIndex0 = (uIndexStart + j) % PL_STATS_MAX_FRAMES; - pl_text("%13.6f", dValues[uActualIndex0]); + gptUI->text("%13.6f", dValues[uActualIndex0]); } } } - pl_end_tab(); + gptUI->end_tab(); } - pl_end_tab_bar(); + gptUI->end_tab_bar(); } pl_temp_allocator_reset(&tTempAllocator); - pl_end_window(); + gptUI->end_window(); } } @@ -796,31 +793,31 @@ pl__show_device_memory(bool* bValue) if(!ptDevice) ptDevice = ptDataRegistry->get_data("device"); - if(pl_begin_window("Device Memory Analyzer", bValue, false)) + if(gptUI->begin_window("Device Memory Analyzer", bValue, false)) { - plDrawLayer* ptFgLayer = pl_get_window_fg_drawlayer(); - const plVec2 tWindowSize = pl_get_window_size(); - const plVec2 tWindowPos = pl_get_window_pos(); + plDrawLayer2D* ptFgLayer = gptUI->get_window_fg_drawlayer(); + const plVec2 tWindowSize = gptUI->get_window_size(); + const plVec2 tWindowPos = gptUI->get_window_pos(); const plVec2 tWindowEnd = pl_add_vec2(tWindowSize, tWindowPos); - const plVec2 tMousePos = pl_get_mouse_pos(); + const plVec2 tMousePos = gptIO->get_mouse_pos(); if(ptDevice->ptGraphics->szLocalMemoryInUse > 1000000000) - pl_text("Device Local Memory: %0.3f gb", (double)ptDevice->ptGraphics->szLocalMemoryInUse / 1000000000); + gptUI->text("Device Local Memory: %0.3f gb", (double)ptDevice->ptGraphics->szLocalMemoryInUse / 1000000000); else if(ptDevice->ptGraphics->szLocalMemoryInUse > 1000000) - pl_text("Device Local Memory: %0.3f mb", (double)ptDevice->ptGraphics->szLocalMemoryInUse / 1000000); + gptUI->text("Device Local Memory: %0.3f mb", (double)ptDevice->ptGraphics->szLocalMemoryInUse / 1000000); else if(ptDevice->ptGraphics->szLocalMemoryInUse > 1000) - pl_text("Device Local Memory: %0.3f kb", (double)ptDevice->ptGraphics->szLocalMemoryInUse / 1000); + gptUI->text("Device Local Memory: %0.3f kb", (double)ptDevice->ptGraphics->szLocalMemoryInUse / 1000); else - pl_text("Device Local Memory: %llu bytes", (double)ptDevice->ptGraphics->szLocalMemoryInUse); + gptUI->text("Device Local Memory: %llu bytes", (double)ptDevice->ptGraphics->szLocalMemoryInUse); if(ptDevice->ptGraphics->szHostMemoryInUse > 1000000000) - pl_text("Host Memory: %0.3f gb", (double)ptDevice->ptGraphics->szHostMemoryInUse / 1000000000); + gptUI->text("Host Memory: %0.3f gb", (double)ptDevice->ptGraphics->szHostMemoryInUse / 1000000000); else if(ptDevice->ptGraphics->szHostMemoryInUse > 1000000) - pl_text("Host Memory: %0.3f mb", (double)ptDevice->ptGraphics->szHostMemoryInUse / 1000000); + gptUI->text("Host Memory: %0.3f mb", (double)ptDevice->ptGraphics->szHostMemoryInUse / 1000000); else if(ptDevice->ptGraphics->szHostMemoryInUse > 1000) - pl_text("Host Memory: %0.3f kb", (double)ptDevice->ptGraphics->szHostMemoryInUse / 1000); + gptUI->text("Host Memory: %0.3f kb", (double)ptDevice->ptGraphics->szHostMemoryInUse / 1000); else - pl_text("Host Memory: %llu bytes", (double)ptDevice->ptGraphics->szHostMemoryInUse); + gptUI->text("Host Memory: %llu bytes", (double)ptDevice->ptGraphics->szHostMemoryInUse); const plDeviceMemoryAllocatorI atAllocators[] = { *gptGpuAllocators->get_local_buddy_allocator(ptDevice), @@ -836,26 +833,26 @@ pl__show_device_memory(bool* bValue) "Device Memory: Staging Cached" }; - pl_push_theme_color(PL_UI_COLOR_BUTTON, &tButtonColor); - pl_push_theme_color(PL_UI_COLOR_BUTTON_ACTIVE, &tButtonColor); - pl_push_theme_color(PL_UI_COLOR_BUTTON_HOVERED, &tButtonColor); + gptUI->push_theme_color(PL_UI_COLOR_BUTTON, &tButtonColor); + gptUI->push_theme_color(PL_UI_COLOR_BUTTON_ACTIVE, &tButtonColor); + gptUI->push_theme_color(PL_UI_COLOR_BUTTON_HOVERED, &tButtonColor); for(uint32_t uAllocatorIndex = 0; uAllocatorIndex < 3; uAllocatorIndex++) { uint32_t uBlockCount = 0; uint32_t uRangeCount = 0; - plDeviceAllocationBlock* sbtBlocks = gptGpuAllocators->get_blocks(&atAllocators[uAllocatorIndex], &uBlockCount); + plDeviceMemoryAllocation* sbtBlocks = gptGpuAllocators->get_blocks(&atAllocators[uAllocatorIndex], &uBlockCount); plDeviceAllocationRange* sbtRanges = gptGpuAllocators->get_ranges(&atAllocators[uAllocatorIndex], &uRangeCount); if(uBlockCount > 0) { - pl_layout_dynamic(0.0f, 1); - pl_separator(); - pl_text(apcAllocatorNames[uAllocatorIndex]); + gptUI->layout_dynamic(0.0f, 1); + gptUI->separator(); + gptUI->text(apcAllocatorNames[uAllocatorIndex]); - pl_layout_template_begin(30.0f); - pl_layout_template_push_static(150.0f); - pl_layout_template_push_variable(300.0f); - pl_layout_template_end(); + gptUI->layout_template_begin(30.0f); + gptUI->layout_template_push_static(150.0f); + gptUI->layout_template_push_variable(300.0f); + gptUI->layout_template_end(); float fWidth0 = -1.0f; float fHeight0 = -1.0f; @@ -867,16 +864,16 @@ pl__show_device_memory(bool* bValue) for(uint32_t i = 0; i < uBlockCount; i++) { - plDeviceAllocationBlock* ptBlock = &sbtBlocks[i]; + plDeviceMemoryAllocation* ptBlock = &sbtBlocks[i]; if(ptBlock->ulSize == 0) continue; char* pcTempBuffer0 = pl_temp_allocator_sprintf(&tTempAllocator, "Block %u: %0.1fMB##%u", iCurrentBlock, ((double)ptBlock->ulSize)/1000000.0, uAllocatorIndex); char* pcTempBuffer1 = pl_temp_allocator_sprintf(&tTempAllocator, "Block %u##%u", iCurrentBlock, uAllocatorIndex); - pl_button(pcTempBuffer0); + gptUI->button(pcTempBuffer0); - plVec2 tCursor0 = pl_get_cursor_pos(); + plVec2 tCursor0 = gptUI->get_cursor_pos(); if(fHeight0 == -1.0f) { @@ -887,12 +884,12 @@ pl__show_device_memory(bool* bValue) const float fWidthAvailable = tWindowEnd.x - tCursor0.x; const float fTotalWidth = fWidthAvailable * ((float)ptBlock->ulSize) / (float)ulMaxBlockSize; - if(ptBlock->ulAddress == 0) - pl_add_rect(ptFgLayer, tCursor0, (plVec2){tCursor0.x + fTotalWidth - 6.0f, 30.0f + tCursor0.y}, tAvailableColor, 1.0f); + if(ptBlock->uHandle == 0) + gptDraw->add_rect(ptFgLayer, tCursor0, (plVec2){tCursor0.x + fTotalWidth - 6.0f, 30.0f + tCursor0.y}, tAvailableColor, 1.0f); else - pl_add_rect_filled(ptFgLayer, tCursor0, (plVec2){tCursor0.x + fTotalWidth, 30.0f + tCursor0.y}, tAvailableColor); - pl_invisible_button(pcTempBuffer1, (plVec2){fTotalWidth, 30.0f}); - if(pl_was_last_item_hovered()) + gptDraw->add_rect_filled(ptFgLayer, tCursor0, (plVec2){tCursor0.x + fTotalWidth, 30.0f + tCursor0.y}, tAvailableColor); + gptUI->invisible_button(pcTempBuffer1, (plVec2){fTotalWidth, 30.0f}); + if(gptUI->was_last_item_hovered()) { ulHoveredBlock = (uint64_t)i; } @@ -905,7 +902,7 @@ pl__show_device_memory(bool* bValue) { plDeviceAllocationRange* ptRange = &sbtRanges[i]; - plDeviceAllocationBlock* ptBlock = &sbtBlocks[ptRange->ulBlockIndex]; + plDeviceMemoryAllocation* ptBlock = &sbtBlocks[ptRange->ulBlockIndex]; if(ptRange->ulUsedSize == 0 || ptRange->ulUsedSize == UINT64_MAX) continue; @@ -917,29 +914,29 @@ pl__show_device_memory(bool* bValue) const float fAvailableWidth = fWidthAvailable * ((float)ptRange->ulTotalSize) / (float)ulMaxBlockSize; const float fYPos = fHeight0 + 34.0f * (float)ptBlock->uCurrentIndex; - pl_add_rect_filled(ptFgLayer, (plVec2){fStartPos, fYPos}, (plVec2){fStartPos + fAvailableWidth, 30.0f + fYPos}, tWastedColor); - pl_add_rect_filled(ptFgLayer, (plVec2){fStartPos, fYPos}, (plVec2){fStartPos + fUsedWidth, 30.0f + fYPos}, tUsedColor); + gptDraw->add_rect_filled(ptFgLayer, (plVec2){fStartPos, fYPos}, (plVec2){fStartPos + fAvailableWidth, 30.0f + fYPos}, tWastedColor); + gptDraw->add_rect_filled(ptFgLayer, (plVec2){fStartPos, fYPos}, (plVec2){fStartPos + fUsedWidth, 30.0f + fYPos}, tUsedColor); if(ptRange->ulBlockIndex == ulHoveredBlock) { const plRect tHitBox = pl_calculate_rect((plVec2){fStartPos, fYPos}, (plVec2){fAvailableWidth, 30}); if(pl_rect_contains_point(&tHitBox, tMousePos)) { - pl_add_rect(ptFgLayer, tHitBox.tMin, tHitBox.tMax, tWhiteColor, 1.0f); - pl_begin_tooltip(); - pl_text(ptRange->acName); - pl_text("Offset: %lu", ptRange->ulOffset); - pl_text("Requested Size: %lu", ptRange->ulUsedSize); - pl_text("Allocated Size: %lu", ptRange->ulTotalSize); - pl_text("Wasted: %lu", ptRange->ulTotalSize - ptRange->ulUsedSize); - pl_end_tooltip(); + gptDraw->add_rect(ptFgLayer, tHitBox.tMin, tHitBox.tMax, tWhiteColor, 1.0f); + gptUI->begin_tooltip(); + gptUI->text(ptRange->acName); + gptUI->text("Offset: %lu", ptRange->ulOffset); + gptUI->text("Requested Size: %lu", ptRange->ulUsedSize); + gptUI->text("Allocated Size: %lu", ptRange->ulTotalSize); + gptUI->text("Wasted: %lu", ptRange->ulTotalSize - ptRange->ulUsedSize); + gptUI->end_tooltip(); } } } } } - pl_pop_theme_color(3); - pl_end_window(); + gptUI->pop_theme_color(3); + gptUI->end_window(); } } @@ -956,21 +953,21 @@ pl__show_logging(bool* bValue) {1.0f, 0.0f, 1.0f, 1.0f} }; - if(pl_begin_window("Logging", bValue, false)) + if(gptUI->begin_window("Logging", bValue, false)) { - const plVec2 tWindowSize = pl_get_window_size(); - const plVec2 tWindowPos = pl_get_window_pos(); + const plVec2 tWindowSize = gptUI->get_window_size(); + const plVec2 tWindowPos = gptUI->get_window_pos(); const plVec2 tWindowEnd = pl_add_vec2(tWindowSize, tWindowPos); static bool bActiveLevels[6] = { true, true, true, true, true, true}; - pl_layout_dynamic(0.0f, 6); - pl_checkbox("Trace", &bActiveLevels[0]); - pl_checkbox("Debug", &bActiveLevels[1]); - pl_checkbox("Info", &bActiveLevels[2]); - pl_checkbox("Warn", &bActiveLevels[3]); - pl_checkbox("Error", &bActiveLevels[4]); - pl_checkbox("Fatal", &bActiveLevels[5]); + gptUI->layout_dynamic(0.0f, 6); + gptUI->checkbox("Trace", &bActiveLevels[0]); + gptUI->checkbox("Debug", &bActiveLevels[1]); + gptUI->checkbox("Info", &bActiveLevels[2]); + gptUI->checkbox("Warn", &bActiveLevels[3]); + gptUI->checkbox("Error", &bActiveLevels[4]); + gptUI->checkbox("Fatal", &bActiveLevels[5]); bool bUseClipper = true; for(uint32_t i = 0; i < 6; i++) @@ -982,8 +979,8 @@ pl__show_logging(bool* bValue) } } - pl_layout_dynamic(0.0f, 1); - if(pl_begin_tab_bar("tab bar")) + gptUI->layout_dynamic(0.0f, 1); + if(gptUI->begin_tab_bar("tab bar")) { uint32_t uChannelCount = 0; plLogChannel* ptChannels = pl_get_log_channels(&uChannelCount); @@ -993,13 +990,13 @@ pl__show_logging(bool* bValue) if(ptChannel->tType & PL_CHANNEL_TYPE_CYCLIC_BUFFER) { - if(pl_begin_tab(ptChannel->pcName)) + if(gptUI->begin_tab(ptChannel->pcName)) { - const plVec2 tCursorPos = pl_get_cursor_pos(); - pl_layout_dynamic(tWindowEnd.y - tCursorPos.y - 20.0f, 1); - if(pl_begin_child(ptChannel->pcName)) + const plVec2 tCursorPos = gptUI->get_cursor_pos(); + gptUI->layout_dynamic(tWindowEnd.y - tCursorPos.y - 20.0f, 1); + if(gptUI->begin_child(ptChannel->pcName)) { - pl_layout_dynamic(0.0f, 1); + gptUI->layout_dynamic(0.0f, 1); const uint64_t uIndexStart = ptChannel->uEntryCount; const uint64_t uLogCount = pl_min(PL_LOG_CYCLIC_BUFFER_SIZE, ptChannel->uEntryCount); @@ -1007,13 +1004,13 @@ pl__show_logging(bool* bValue) if(bUseClipper) { plUiClipper tClipper = {(uint32_t)uLogCount}; - while(pl_step_clipper(&tClipper)) + while(gptUI->step_clipper(&tClipper)) { for(uint32_t j = tClipper.uDisplayStart; j < tClipper.uDisplayEnd; j++) { uint32_t uActualIndex0 = (uIndexStart + j) % (uint32_t)uLogCount; const plLogEntry* ptEntry = &ptChannel->atEntries[uActualIndex0]; - pl_color_text(atColors[ptEntry->uLevel / 1000 - 5], &ptChannel->pcBuffer0[ptEntry->uOffset + ptChannel->uBufferCapacity * (ptEntry->uGeneration % 2)]); + gptUI->color_text(atColors[ptEntry->uLevel / 1000 - 5], &ptChannel->pcBuffer0[ptEntry->uOffset + ptChannel->uBufferCapacity * (ptEntry->uGeneration % 2)]); } } } @@ -1024,33 +1021,33 @@ pl__show_logging(bool* bValue) uint32_t uActualIndex0 = (uIndexStart + j) % (uint32_t)uLogCount; const plLogEntry* ptEntry = &ptChannel->atEntries[uActualIndex0]; if(bActiveLevels[ptEntry->uLevel / 1000 - 5]) - pl_color_text(atColors[ptEntry->uLevel / 1000 - 5], &ptChannel->pcBuffer0[ptEntry->uOffset + ptChannel->uBufferCapacity * (ptEntry->uGeneration % 2)]); + gptUI->color_text(atColors[ptEntry->uLevel / 1000 - 5], &ptChannel->pcBuffer0[ptEntry->uOffset + ptChannel->uBufferCapacity * (ptEntry->uGeneration % 2)]); } } - pl_end_child(); + gptUI->end_child(); } - pl_end_tab(); + gptUI->end_tab(); } } else if(ptChannel->tType & PL_CHANNEL_TYPE_BUFFER) { - if(pl_begin_tab(ptChannel->pcName)) + if(gptUI->begin_tab(ptChannel->pcName)) { - const plVec2 tCursorPos = pl_get_cursor_pos(); - pl_layout_dynamic(tWindowEnd.y - tCursorPos.y - 20.0f, 1); - if(pl_begin_child(ptChannel->pcName)) + const plVec2 tCursorPos = gptUI->get_cursor_pos(); + gptUI->layout_dynamic(tWindowEnd.y - tCursorPos.y - 20.0f, 1); + if(gptUI->begin_child(ptChannel->pcName)) { - pl_layout_dynamic(0.0f, 1); + gptUI->layout_dynamic(0.0f, 1); if(bUseClipper) { plUiClipper tClipper = {(uint32_t)ptChannel->uEntryCount}; - while(pl_step_clipper(&tClipper)) + while(gptUI->step_clipper(&tClipper)) { for(uint32_t j = tClipper.uDisplayStart; j < tClipper.uDisplayEnd; j++) { plLogEntry* ptEntry = &ptChannel->pEntries[j]; - pl_color_text(atColors[ptEntry->uLevel / 1000 - 5], &ptChannel->pcBuffer0[ptEntry->uOffset]); + gptUI->color_text(atColors[ptEntry->uLevel / 1000 - 5], &ptChannel->pcBuffer0[ptEntry->uOffset]); } } } @@ -1060,19 +1057,19 @@ pl__show_logging(bool* bValue) { plLogEntry* ptEntry = &ptChannel->pEntries[j]; if(bActiveLevels[ptEntry->uLevel / 1000 - 5]) - pl_color_text(atColors[ptEntry->uLevel / 1000 - 5], &ptChannel->pcBuffer0[ptEntry->uOffset]); + gptUI->color_text(atColors[ptEntry->uLevel / 1000 - 5], &ptChannel->pcBuffer0[ptEntry->uOffset]); } } - pl_end_child(); + gptUI->end_child(); } - pl_end_tab(); + gptUI->end_tab(); } } } - pl_end_tab_bar(); + gptUI->end_tab_bar(); } - pl_end_window(); + gptUI->end_window(); } } @@ -1089,11 +1086,13 @@ pl_load_ext(plApiRegistryI* ptApiRegistry, bool bReload) pl_set_memory_context(ptMemoryCtx); pl_set_profile_context(ptDataRegistry->get_data("profile")); pl_set_log_context(ptDataRegistry->get_data("log")); - pl_set_context(ptDataRegistry->get_data("context")); ptStatsApi = ptApiRegistry->first(PL_API_STATS); gptGpuAllocators = ptApiRegistry->first(PL_API_GPU_ALLOCATORS); - ptIOCtx = pl_get_io(); + gptDraw = ptApiRegistry->first(PL_API_DRAW); + gptUI = ptApiRegistry->first(PL_API_UI); + gptIO = ptApiRegistry->first(PL_API_IO); + ptIOCtx = gptIO->get_io(); if(bReload) { diff --git a/extensions/pl_draw_3d_ext.c b/extensions/pl_draw_3d_ext.c deleted file mode 100644 index 00e0f820..00000000 --- a/extensions/pl_draw_3d_ext.c +++ /dev/null @@ -1,906 +0,0 @@ -/* - pl_draw_3d_ext.c -*/ - -/* -Index of this file: -// [SECTION] includes -// [SECTION] internal structs -// [SECTION] global data -// [SECTION] required apis -// [SECTION] internal api -// [SECTION] public api implementation -// [SECTION] internal api implementation -// [SECTION] extension loading -*/ - -//----------------------------------------------------------------------------- -// [SECTION] includes -//----------------------------------------------------------------------------- - -#include "pilotlight.h" -#define PL_MATH_INCLUDE_FUNCTIONS -#include "pl_draw_3d_ext.h" -#include "pl_ds.h" -#include "pl_graphics_ext.h" -#include "pl_gpu_allocators_ext.h" -#include "pl_memory.h" - -//----------------------------------------------------------------------------- -// [SECTION] internal structs -//----------------------------------------------------------------------------- - -typedef struct _plDrawVertex3DSolid -{ - float afPos[3]; - uint32_t uColor; -} plDrawVertex3DSolid; - -typedef struct _plDrawVertex3DLine -{ - float afPos[3]; - float fDirection; - float fThickness; - float fMultiply; - float afPosOther[3]; - uint32_t uColor; -} plDrawVertex3DLine; - -typedef struct _plDrawList3D -{ - plDrawVertex3DSolid* sbtSolidVertexBuffer; - uint32_t* sbtSolidIndexBuffer; - plDrawVertex3DLine* sbtLineVertexBuffer; - uint32_t* sbtLineIndexBuffer; -} plDrawList3D; - -typedef struct _plPipelineEntry -{ - plRenderPassHandle tRenderPass; - uint32_t uMSAASampleCount; - plShaderHandle tRegularPipeline; - plShaderHandle tSecondaryPipeline; - pl3DDrawFlags tFlags; - uint32_t uSubpassIndex; -} plPipelineEntry; - -typedef struct _pl3DBufferInfo -{ - // vertex buffer - plBufferHandle tVertexBuffer; - uint32_t uVertexBufferSize; - uint32_t uVertexBufferOffset; - - // index buffer - -} pl3DBufferInfo; - -typedef struct _plDraw3dContext -{ - plGraphics* ptGraphics; - plDeviceMemoryAllocatorI* ptStagingUnCachedAllocator; - plPipelineEntry* sbtPipelineEntries; - - plPoolAllocator tDrawlistPool; - char acPoolBuffer[sizeof(plDrawList3D) * (PL_MAX_3D_DRAWLISTS + 1)]; - plDrawList3D* aptDrawlists[PL_MAX_3D_DRAWLISTS]; - uint32_t uDrawlistCount; - - plBufferHandle atIndexBuffer[PL_FRAMES_IN_FLIGHT]; - uint32_t auIndexBufferSize[PL_FRAMES_IN_FLIGHT]; - uint32_t auIndexBufferOffset[PL_FRAMES_IN_FLIGHT]; - pl3DBufferInfo at3DBufferInfo[PL_FRAMES_IN_FLIGHT]; - pl3DBufferInfo atLineBufferInfo[PL_FRAMES_IN_FLIGHT]; -} plDraw3dContext; - -//----------------------------------------------------------------------------- -// [SECTION] global data -//----------------------------------------------------------------------------- - -static plDraw3dContext* gptCtx = NULL; - -//----------------------------------------------------------------------------- -// [SECTION] required apis -//----------------------------------------------------------------------------- - -static const plDeviceI* gptDevice = NULL; -static const plGraphicsI* gptGfx = NULL; -static const plGPUAllocatorsI* gptGpuAllocators = NULL; - -//----------------------------------------------------------------------------- -// [SECTION] internal api -//----------------------------------------------------------------------------- - -static plBufferHandle pl__create_staging_buffer(const plBufferDescription*, const char* pcName, uint32_t uIdentifier); -static const plPipelineEntry* pl__get_pipeline (plRenderPassHandle, uint32_t uMSAASampleCount, pl3DDrawFlags, uint32_t uSubpassIndex); - -//----------------------------------------------------------------------------- -// [SECTION] public api implementation -//----------------------------------------------------------------------------- - -static void -pl_initialize(plGraphics* ptGraphics) -{ - gptCtx->ptGraphics = ptGraphics; - gptCtx->ptStagingUnCachedAllocator = gptGpuAllocators->get_staging_uncached_allocator(&ptGraphics->tDevice); - - pl_sb_reserve(gptCtx->sbtPipelineEntries, 32); - - // create initial buffers - const plBufferDescription tIndexBufferDesc = { - .tUsage = PL_BUFFER_USAGE_INDEX | PL_BUFFER_USAGE_STAGING, - .uByteSize = 4096 - }; - - const plBufferDescription tVertexBufferDesc = { - .tUsage = PL_BUFFER_USAGE_VERTEX | PL_BUFFER_USAGE_STAGING, - .uByteSize = 4096 - }; - - for(uint32_t i = 0; i < PL_FRAMES_IN_FLIGHT; i++) - { - gptCtx->auIndexBufferSize[i] = 4096; - gptCtx->at3DBufferInfo[i].uVertexBufferSize = 4096; - gptCtx->atLineBufferInfo[i].uVertexBufferSize = 4096; - gptCtx->atIndexBuffer[i] = pl__create_staging_buffer(&tIndexBufferDesc, "3d draw idx buffer", i); - gptCtx->at3DBufferInfo[i].tVertexBuffer= pl__create_staging_buffer(&tVertexBufferDesc, "3d draw vtx buffer", i); - gptCtx->atLineBufferInfo[i].tVertexBuffer= pl__create_staging_buffer(&tVertexBufferDesc, "3d line draw vtx buffer", i); - } - - size_t szBufferSize = sizeof(plDrawList3D) * (PL_MAX_3D_DRAWLISTS + 1); - pl_pool_allocator_init(&gptCtx->tDrawlistPool, PL_MAX_3D_DRAWLISTS, sizeof(plDrawList3D), 0, &szBufferSize, gptCtx->acPoolBuffer); -} - -static void -pl_cleanup(void) -{ - for(uint32_t i = 0u; i < gptCtx->uDrawlistCount; i++) - { - plDrawList3D* ptDrawlist = gptCtx->aptDrawlists[i]; - pl_sb_free(ptDrawlist->sbtSolidIndexBuffer); - pl_sb_free(ptDrawlist->sbtSolidVertexBuffer); - pl_sb_free(ptDrawlist->sbtLineVertexBuffer); - pl_sb_free(ptDrawlist->sbtLineIndexBuffer); - } - pl_sb_free(gptCtx->sbtPipelineEntries); -} - -static plDrawList3D* -pl_request_drawlist(void) -{ - plDrawList3D* ptDrawlist = pl_pool_allocator_alloc(&gptCtx->tDrawlistPool); - PL_ASSERT(ptDrawlist && "no drawlist available"); - - pl_sb_reserve(ptDrawlist->sbtLineIndexBuffer, 1024); - pl_sb_reserve(ptDrawlist->sbtLineVertexBuffer, 1024); - pl_sb_reserve(ptDrawlist->sbtSolidIndexBuffer, 1024); - pl_sb_reserve(ptDrawlist->sbtSolidVertexBuffer, 1024); - - gptCtx->aptDrawlists[gptCtx->uDrawlistCount] = ptDrawlist; - gptCtx->uDrawlistCount++; - return ptDrawlist; -} - -static void -pl_return_drawlist(plDrawList3D* ptDrawlist) -{ - - pl_sb_free(ptDrawlist->sbtLineIndexBuffer); - pl_sb_free(ptDrawlist->sbtLineVertexBuffer); - pl_sb_free(ptDrawlist->sbtSolidIndexBuffer); - pl_sb_free(ptDrawlist->sbtSolidVertexBuffer); - - uint32_t uCurrentIndex = 0; - for(uint32_t i = 0; i < gptCtx->uDrawlistCount; i++) - { - if(gptCtx->aptDrawlists[i] != ptDrawlist) // skip returning drawlist - { - plDrawList3D* ptCurrentDrawlist = gptCtx->aptDrawlists[i]; - gptCtx->aptDrawlists[uCurrentIndex] = ptCurrentDrawlist; - uCurrentIndex++; - } - } - pl_pool_allocator_free(&gptCtx->tDrawlistPool, ptDrawlist); - gptCtx->uDrawlistCount--; -} - -static void -pl_new_draw_3d_frame(void) -{ - // reset 3d drawlists - for(uint32_t i = 0u; i < gptCtx->uDrawlistCount; i++) - { - plDrawList3D* ptDrawlist = gptCtx->aptDrawlists[i]; - - pl_sb_reset(ptDrawlist->sbtSolidVertexBuffer); - pl_sb_reset(ptDrawlist->sbtLineVertexBuffer); - pl_sb_reset(ptDrawlist->sbtSolidIndexBuffer); - pl_sb_reset(ptDrawlist->sbtLineIndexBuffer); - } - - // reset buffer offsets - for(uint32_t i = 0; i < PL_FRAMES_IN_FLIGHT; i++) - { - gptCtx->at3DBufferInfo[i].uVertexBufferOffset = 0; - gptCtx->atLineBufferInfo[i].uVertexBufferOffset = 0; - gptCtx->auIndexBufferOffset[i] = 0; - } -} - -static void -pl__submit_drawlist(plDrawList3D* ptDrawlist, plRenderEncoder tEncoder, float fWidth, float fHeight, const plMat4* ptMVP, pl3DDrawFlags tFlags, uint32_t uMSAASampleCount) -{ - plGraphics* ptGfx = gptCtx->ptGraphics; - - const plPipelineEntry* ptEntry = pl__get_pipeline(tEncoder.tRenderPassHandle, uMSAASampleCount, tFlags, tEncoder._uCurrentSubpass); - - const float fAspectRatio = fWidth / fHeight; - - // regular 3D - if(pl_sb_size(ptDrawlist->sbtSolidVertexBuffer) > 0u) - { - //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~vertex buffer prep~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - // ensure gpu vertex buffer size is adequate - const uint32_t uVtxBufSzNeeded = sizeof(plDrawVertex3DSolid) * pl_sb_size(ptDrawlist->sbtSolidVertexBuffer); - - pl3DBufferInfo* ptBufferInfo = &gptCtx->at3DBufferInfo[ptGfx->uCurrentFrameIndex]; - - // space left in vertex buffer - const uint32_t uAvailableVertexBufferSpace = ptBufferInfo->uVertexBufferSize - ptBufferInfo->uVertexBufferOffset; - - // grow buffer if not enough room - if(uVtxBufSzNeeded >= uAvailableVertexBufferSpace) - { - - gptDevice->queue_buffer_for_deletion(&ptGfx->tDevice, ptBufferInfo->tVertexBuffer); - - const plBufferDescription tBufferDesc = { - .tUsage = PL_BUFFER_USAGE_VERTEX | PL_BUFFER_USAGE_STAGING, - .uByteSize = pl_max(ptBufferInfo->uVertexBufferSize * 2, uVtxBufSzNeeded + uAvailableVertexBufferSpace) - }; - ptBufferInfo->uVertexBufferSize = tBufferDesc.uByteSize; - - ptBufferInfo->tVertexBuffer = pl__create_staging_buffer(&tBufferDesc, "3d draw vtx buffer", ptGfx->uCurrentFrameIndex); - } - - // vertex GPU data transfer - plBuffer* ptVertexBuffer = gptDevice->get_buffer(&ptGfx->tDevice, ptBufferInfo->tVertexBuffer); - char* pucMappedVertexBufferLocation = ptVertexBuffer->tMemoryAllocation.pHostMapped; - memcpy(&pucMappedVertexBufferLocation[ptBufferInfo->uVertexBufferOffset], ptDrawlist->sbtSolidVertexBuffer, sizeof(plDrawVertex3DSolid) * pl_sb_size(ptDrawlist->sbtSolidVertexBuffer)); - - //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~index buffer prep~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - // ensure gpu index buffer size is adequate - const uint32_t uIdxBufSzNeeded = sizeof(uint32_t) * pl_sb_size(ptDrawlist->sbtSolidIndexBuffer); - - // space left in index buffer - const uint32_t uAvailableIndexBufferSpace = gptCtx->auIndexBufferSize[ptGfx->uCurrentFrameIndex] - gptCtx->auIndexBufferOffset[ptGfx->uCurrentFrameIndex]; - - if(uIdxBufSzNeeded >= uAvailableIndexBufferSpace) - { - gptDevice->queue_buffer_for_deletion(&ptGfx->tDevice, gptCtx->atIndexBuffer[ptGfx->uCurrentFrameIndex]); - - const plBufferDescription tBufferDesc = { - .tUsage = PL_BUFFER_USAGE_INDEX | PL_BUFFER_USAGE_STAGING, - .uByteSize = pl_max(gptCtx->auIndexBufferSize[ptGfx->uCurrentFrameIndex] * 2, uIdxBufSzNeeded + uAvailableIndexBufferSpace) - }; - gptCtx->auIndexBufferSize[ptGfx->uCurrentFrameIndex] = tBufferDesc.uByteSize; - - gptCtx->atIndexBuffer[ptGfx->uCurrentFrameIndex] = pl__create_staging_buffer(&tBufferDesc, "3d draw idx buffer", ptGfx->uCurrentFrameIndex); - } - - // index GPU data transfer - plBuffer* ptIndexBuffer = gptDevice->get_buffer(&ptGfx->tDevice, gptCtx->atIndexBuffer[ptGfx->uCurrentFrameIndex]); - char* pucMappedIndexBufferLocation = ptIndexBuffer->tMemoryAllocation.pHostMapped; - memcpy(&pucMappedIndexBufferLocation[gptCtx->auIndexBufferOffset[ptGfx->uCurrentFrameIndex]], ptDrawlist->sbtSolidIndexBuffer, sizeof(uint32_t) * pl_sb_size(ptDrawlist->sbtSolidIndexBuffer)); - - plDynamicBinding tSolidDynamicData = gptDevice->allocate_dynamic_data(&ptGfx->tDevice, sizeof(plMat4)); - plMat4* ptSolidDynamicData = (plMat4*)tSolidDynamicData.pcData; - *ptSolidDynamicData = *ptMVP; - - gptGfx->bind_vertex_buffer(&tEncoder, ptBufferInfo->tVertexBuffer); - gptGfx->bind_shader(&tEncoder, ptEntry->tRegularPipeline); - gptGfx->bind_graphics_bind_groups(&tEncoder, ptEntry->tRegularPipeline, 0, 0, NULL, &tSolidDynamicData); - - const int32_t iVertexOffset = ptBufferInfo->uVertexBufferOffset / sizeof(plDrawVertex3DSolid); - const int32_t iIndexOffset = gptCtx->auIndexBufferOffset[ptGfx->uCurrentFrameIndex] / sizeof(uint32_t); - - const plDrawIndex tDrawIndex = { - .tIndexBuffer = gptCtx->atIndexBuffer[ptGfx->uCurrentFrameIndex], - .uIndexCount = pl_sb_size(ptDrawlist->sbtSolidIndexBuffer), - .uIndexStart = iIndexOffset, - .uInstance = 0, - .uInstanceCount = 1, - .uVertexStart = iVertexOffset - }; - - gptGfx->draw_indexed(&tEncoder, 1, &tDrawIndex); - - // bump vertex & index buffer offset - ptBufferInfo->uVertexBufferOffset += uVtxBufSzNeeded; - gptCtx->auIndexBufferOffset[ptGfx->uCurrentFrameIndex] += uIdxBufSzNeeded; - } - - // 3D lines - if(pl_sb_size(ptDrawlist->sbtLineVertexBuffer) > 0u) - { - //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~vertex buffer prep~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - // ensure gpu vertex buffer size is adequate - const uint32_t uVtxBufSzNeeded = sizeof(plDrawVertex3DLine) * pl_sb_size(ptDrawlist->sbtLineVertexBuffer); - - pl3DBufferInfo* ptBufferInfo = &gptCtx->atLineBufferInfo[ptGfx->uCurrentFrameIndex]; - - // space left in vertex buffer - const uint32_t uAvailableVertexBufferSpace = ptBufferInfo->uVertexBufferSize - ptBufferInfo->uVertexBufferOffset; - - // grow buffer if not enough room - if(uVtxBufSzNeeded >= uAvailableVertexBufferSpace) - { - gptDevice->queue_buffer_for_deletion(&ptGfx->tDevice, ptBufferInfo->tVertexBuffer); - - const plBufferDescription tBufferDesc = { - .tUsage = PL_BUFFER_USAGE_VERTEX | PL_BUFFER_USAGE_STAGING, - .uByteSize = pl_max(ptBufferInfo->uVertexBufferSize * 2, uVtxBufSzNeeded + uAvailableVertexBufferSpace) - }; - ptBufferInfo->uVertexBufferSize = tBufferDesc.uByteSize; - - ptBufferInfo->tVertexBuffer = pl__create_staging_buffer(&tBufferDesc, "3d draw vtx buffer", ptGfx->uCurrentFrameIndex); - } - - // vertex GPU data transfer - plBuffer* ptVertexBuffer = gptDevice->get_buffer(&ptGfx->tDevice, ptBufferInfo->tVertexBuffer); - char* pucMappedVertexBufferLocation = ptVertexBuffer->tMemoryAllocation.pHostMapped; - memcpy(&pucMappedVertexBufferLocation[ptBufferInfo->uVertexBufferOffset], ptDrawlist->sbtLineVertexBuffer, sizeof(plDrawVertex3DLine) * pl_sb_size(ptDrawlist->sbtLineVertexBuffer)); - - //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~index buffer prep~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - // ensure gpu index buffer size is adequate - const uint32_t uIdxBufSzNeeded = sizeof(uint32_t) * pl_sb_size(ptDrawlist->sbtLineIndexBuffer); - - // space left in index buffer - const uint32_t uAvailableIndexBufferSpace = gptCtx->auIndexBufferSize[ptGfx->uCurrentFrameIndex] - gptCtx->auIndexBufferOffset[ptGfx->uCurrentFrameIndex]; - - if(uIdxBufSzNeeded >= uAvailableIndexBufferSpace) - { - - gptDevice->queue_buffer_for_deletion(&ptGfx->tDevice, gptCtx->atIndexBuffer[ptGfx->uCurrentFrameIndex]); - - const plBufferDescription tBufferDesc = { - .tUsage = PL_BUFFER_USAGE_INDEX | PL_BUFFER_USAGE_STAGING, - .uByteSize = pl_max(gptCtx->auIndexBufferSize[ptGfx->uCurrentFrameIndex] * 2, uIdxBufSzNeeded + uAvailableIndexBufferSpace) - }; - gptCtx->auIndexBufferSize[ptGfx->uCurrentFrameIndex] = tBufferDesc.uByteSize; - - gptCtx->atIndexBuffer[ptGfx->uCurrentFrameIndex] = pl__create_staging_buffer(&tBufferDesc, "3d draw idx buffer", ptGfx->uCurrentFrameIndex); - } - - // index GPU data transfer - plBuffer* ptIndexBuffer = gptDevice->get_buffer(&ptGfx->tDevice, gptCtx->atIndexBuffer[ptGfx->uCurrentFrameIndex]); - char* pucMappedIndexBufferLocation = ptIndexBuffer->tMemoryAllocation.pHostMapped; - memcpy(&pucMappedIndexBufferLocation[gptCtx->auIndexBufferOffset[ptGfx->uCurrentFrameIndex]], ptDrawlist->sbtLineIndexBuffer, sizeof(uint32_t) * pl_sb_size(ptDrawlist->sbtLineIndexBuffer)); - - typedef struct _plLineDynamiceData - { - plMat4 tMVP; - float fAspect; - int padding[3]; - } plLineDynamiceData; - - plDynamicBinding tLineDynamicData = gptDevice->allocate_dynamic_data(&ptGfx->tDevice, sizeof(plLineDynamiceData)); - plLineDynamiceData* ptLineDynamicData = (plLineDynamiceData*)tLineDynamicData.pcData; - ptLineDynamicData->tMVP = *ptMVP; - ptLineDynamicData->fAspect = fAspectRatio; - - gptGfx->bind_vertex_buffer(&tEncoder, ptBufferInfo->tVertexBuffer); - gptGfx->bind_shader(&tEncoder, ptEntry->tSecondaryPipeline); - gptGfx->bind_graphics_bind_groups(&tEncoder, ptEntry->tSecondaryPipeline, 0, 0, NULL, &tLineDynamicData); - - const int32_t iVertexOffset = ptBufferInfo->uVertexBufferOffset / sizeof(plDrawVertex3DLine); - const int32_t iIndexOffset = gptCtx->auIndexBufferOffset[ptGfx->uCurrentFrameIndex] / sizeof(uint32_t); - - const plDrawIndex tDrawIndex = { - .tIndexBuffer = gptCtx->atIndexBuffer[ptGfx->uCurrentFrameIndex], - .uIndexCount = pl_sb_size(ptDrawlist->sbtLineIndexBuffer), - .uIndexStart = iIndexOffset, - .uInstance = 0, - .uInstanceCount = 1, - .uVertexStart = iVertexOffset - }; - - gptGfx->draw_indexed(&tEncoder, 1, &tDrawIndex); - - // bump vertex & index buffer offset - ptBufferInfo->uVertexBufferOffset += uVtxBufSzNeeded; - gptCtx->auIndexBufferOffset[ptGfx->uCurrentFrameIndex] += uIdxBufSzNeeded; - } -} - -static void -pl__add_triangle_filled(plDrawList3D* ptDrawlist, plVec3 tP0, plVec3 tP1, plVec3 tP2, plVec4 tColor) -{ - - pl_sb_reserve(ptDrawlist->sbtSolidVertexBuffer, pl_sb_size(ptDrawlist->sbtSolidVertexBuffer) + 3); - pl_sb_reserve(ptDrawlist->sbtSolidIndexBuffer, pl_sb_size(ptDrawlist->sbtSolidIndexBuffer) + 3); - - const uint32_t uVertexStart = pl_sb_size(ptDrawlist->sbtSolidVertexBuffer); - - uint32_t tU32Color = 0; - tU32Color = (uint32_t) (255.0f * tColor.r + 0.5f); - tU32Color |= (uint32_t) (255.0f * tColor.g + 0.5f) << 8; - tU32Color |= (uint32_t) (255.0f * tColor.b + 0.5f) << 16; - tU32Color |= (uint32_t) (255.0f * tColor.a + 0.5f) << 24; - - pl_sb_push(ptDrawlist->sbtSolidVertexBuffer, ((plDrawVertex3DSolid){ {tP0.x, tP0.y, tP0.z}, tU32Color})); - pl_sb_push(ptDrawlist->sbtSolidVertexBuffer, ((plDrawVertex3DSolid){ {tP1.x, tP1.y, tP1.z}, tU32Color})); - pl_sb_push(ptDrawlist->sbtSolidVertexBuffer, ((plDrawVertex3DSolid){ {tP2.x, tP2.y, tP2.z}, tU32Color})); - - pl_sb_push(ptDrawlist->sbtSolidIndexBuffer, uVertexStart + 0); - pl_sb_push(ptDrawlist->sbtSolidIndexBuffer, uVertexStart + 1); - pl_sb_push(ptDrawlist->sbtSolidIndexBuffer, uVertexStart + 2); -} - -static void -pl__add_line(plDrawList3D* ptDrawlist, plVec3 tP0, plVec3 tP1, plVec4 tColor, float fThickness) -{ - uint32_t tU32Color = 0; - tU32Color = (uint32_t) (255.0f * tColor.r + 0.5f); - tU32Color |= (uint32_t) (255.0f * tColor.g + 0.5f) << 8; - tU32Color |= (uint32_t) (255.0f * tColor.b + 0.5f) << 16; - tU32Color |= (uint32_t) (255.0f * tColor.a + 0.5f) << 24; - - pl_sb_reserve(ptDrawlist->sbtLineVertexBuffer, pl_sb_size(ptDrawlist->sbtLineVertexBuffer) + 4); - pl_sb_reserve(ptDrawlist->sbtLineIndexBuffer, pl_sb_size(ptDrawlist->sbtLineIndexBuffer) + 6); - - plDrawVertex3DLine tNewVertex0 = { - {tP0.x, tP0.y, tP0.z}, - -1.0f, - fThickness, - 1.0f, - {tP1.x, tP1.y, tP1.z}, - tU32Color - }; - - plDrawVertex3DLine tNewVertex1 = { - {tP1.x, tP1.y, tP1.z}, - -1.0f, - fThickness, - -1.0f, - {tP0.x, tP0.y, tP0.z}, - tU32Color - }; - - const uint32_t uVertexStart = pl_sb_size(ptDrawlist->sbtLineVertexBuffer); - pl_sb_push(ptDrawlist->sbtLineVertexBuffer, tNewVertex0); - pl_sb_push(ptDrawlist->sbtLineVertexBuffer, tNewVertex1); - - tNewVertex0.fDirection = 1.0f; - tNewVertex1.fDirection = 1.0f; - pl_sb_push(ptDrawlist->sbtLineVertexBuffer, tNewVertex1); - pl_sb_push(ptDrawlist->sbtLineVertexBuffer, tNewVertex0); - - pl_sb_push(ptDrawlist->sbtLineIndexBuffer, uVertexStart + 0); - pl_sb_push(ptDrawlist->sbtLineIndexBuffer, uVertexStart + 1); - pl_sb_push(ptDrawlist->sbtLineIndexBuffer, uVertexStart + 2); - - pl_sb_push(ptDrawlist->sbtLineIndexBuffer, uVertexStart + 0); - pl_sb_push(ptDrawlist->sbtLineIndexBuffer, uVertexStart + 2); - pl_sb_push(ptDrawlist->sbtLineIndexBuffer, uVertexStart + 3); -} - -static void -pl__add_point(plDrawList3D* ptDrawlist, plVec3 tP, plVec4 tColor, float fLength, float fThickness) -{ - const plVec3 aatVerticies[6] = { - { tP.x - fLength / 2.0f, tP.y, tP.z}, - { tP.x + fLength / 2.0f, tP.y, tP.z}, - { tP.x, tP.y - fLength / 2.0f, tP.z}, - { tP.x, tP.y + fLength / 2.0f, tP.z}, - { tP.x, tP.y, tP.z - fLength / 2.0f}, - { tP.x, tP.y, tP.z + fLength / 2.0f} - }; - - pl__add_line(ptDrawlist, aatVerticies[0], aatVerticies[1], tColor, fThickness); - pl__add_line(ptDrawlist, aatVerticies[2], aatVerticies[3], tColor, fThickness); - pl__add_line(ptDrawlist, aatVerticies[4], aatVerticies[5], tColor, fThickness); -} - -static void -pl__add_transform(plDrawList3D* ptDrawlist, const plMat4* ptTransform, float fLength, float fThickness) -{ - - const plVec3 tOrigin = pl_mul_mat4_vec3(ptTransform, (plVec3){0.0f, 0.0f, 0.0f}); - const plVec3 tXAxis = pl_mul_mat4_vec3(ptTransform, (plVec3){fLength, 0.0f, 0.0f}); - const plVec3 tYAxis = pl_mul_mat4_vec3(ptTransform, (plVec3){0.0f, fLength, 0.0f}); - const plVec3 tZAxis = pl_mul_mat4_vec3(ptTransform, (plVec3){0.0f, 0.0f, fLength}); - - pl__add_line(ptDrawlist, tOrigin, tXAxis, (plVec4){1.0f, 0.0f, 0.0f, 1.0f}, fThickness); - pl__add_line(ptDrawlist, tOrigin, tYAxis, (plVec4){0.0f, 1.0f, 0.0f, 1.0f}, fThickness); - pl__add_line(ptDrawlist, tOrigin, tZAxis, (plVec4){0.0f, 0.0f, 1.0f, 1.0f}, fThickness); -} - -static void -pl__add_frustum(plDrawList3D* ptDrawlist, const plMat4* ptTransform, float fYFov, float fAspect, float fNearZ, float fFarZ, plVec4 tColor, float fThickness) -{ - const float fSmallHeight = tanf(fYFov / 2.0f) * fNearZ; - const float fSmallWidth = fSmallHeight * fAspect; - const float fBigHeight = tanf(fYFov / 2.0f) * fFarZ; - const float fBigWidth = fBigHeight * fAspect; - - const plVec3 atVerticies[8] = { - pl_mul_mat4_vec3(ptTransform, (plVec3){ fSmallWidth, fSmallHeight, fNearZ}), - pl_mul_mat4_vec3(ptTransform, (plVec3){ fSmallWidth, -fSmallHeight, fNearZ}), - pl_mul_mat4_vec3(ptTransform, (plVec3){ -fSmallWidth, -fSmallHeight, fNearZ}), - pl_mul_mat4_vec3(ptTransform, (plVec3){ -fSmallWidth, fSmallHeight, fNearZ}), - pl_mul_mat4_vec3(ptTransform, (plVec3){ fBigWidth, fBigHeight, fFarZ}), - pl_mul_mat4_vec3(ptTransform, (plVec3){ fBigWidth, -fBigHeight, fFarZ}), - pl_mul_mat4_vec3(ptTransform, (plVec3){ -fBigWidth, -fBigHeight, fFarZ}), - pl_mul_mat4_vec3(ptTransform, (plVec3){ -fBigWidth, fBigHeight, fFarZ}) - }; - - pl__add_line(ptDrawlist, atVerticies[0], atVerticies[1], tColor, fThickness); - pl__add_line(ptDrawlist, atVerticies[1], atVerticies[2], tColor, fThickness); - pl__add_line(ptDrawlist, atVerticies[2], atVerticies[3], tColor, fThickness); - pl__add_line(ptDrawlist, atVerticies[3], atVerticies[0], tColor, fThickness); - pl__add_line(ptDrawlist, atVerticies[0], atVerticies[4], tColor, fThickness); - pl__add_line(ptDrawlist, atVerticies[1], atVerticies[5], tColor, fThickness); - pl__add_line(ptDrawlist, atVerticies[2], atVerticies[6], tColor, fThickness); - pl__add_line(ptDrawlist, atVerticies[3], atVerticies[7], tColor, fThickness); - pl__add_line(ptDrawlist, atVerticies[4], atVerticies[5], tColor, fThickness); - pl__add_line(ptDrawlist, atVerticies[5], atVerticies[6], tColor, fThickness); - pl__add_line(ptDrawlist, atVerticies[6], atVerticies[7], tColor, fThickness); - pl__add_line(ptDrawlist, atVerticies[7], atVerticies[4], tColor, fThickness); -} - -static void -pl__add_centered_box(plDrawList3D* ptDrawlist, plVec3 tCenter, float fWidth, float fHeight, float fDepth, plVec4 tColor, float fThickness) -{ - const plVec3 tWidthVec = {fWidth / 2.0f, 0.0f, 0.0f}; - const plVec3 tHeightVec = {0.0f, fHeight / 2.0f, 0.0f}; - const plVec3 tDepthVec = {0.0f, 0.0f, fDepth / 2.0f}; - - const plVec3 atVerticies[8] = { - { tCenter.x - fWidth / 2.0f, tCenter.y + fHeight / 2.0f, tCenter.z - fDepth / 2.0f}, - { tCenter.x - fWidth / 2.0f, tCenter.y - fHeight / 2.0f, tCenter.z - fDepth / 2.0f}, - { tCenter.x + fWidth / 2.0f, tCenter.y - fHeight / 2.0f, tCenter.z - fDepth / 2.0f}, - { tCenter.x + fWidth / 2.0f, tCenter.y + fHeight / 2.0f, tCenter.z - fDepth / 2.0f}, - { tCenter.x - fWidth / 2.0f, tCenter.y + fHeight / 2.0f, tCenter.z + fDepth / 2.0f}, - { tCenter.x - fWidth / 2.0f, tCenter.y - fHeight / 2.0f, tCenter.z + fDepth / 2.0f}, - { tCenter.x + fWidth / 2.0f, tCenter.y - fHeight / 2.0f, tCenter.z + fDepth / 2.0f}, - { tCenter.x + fWidth / 2.0f, tCenter.y + fHeight / 2.0f, tCenter.z + fDepth / 2.0f} - }; - - pl__add_line(ptDrawlist, atVerticies[0], atVerticies[1], tColor, fThickness); - pl__add_line(ptDrawlist, atVerticies[1], atVerticies[2], tColor, fThickness); - pl__add_line(ptDrawlist, atVerticies[2], atVerticies[3], tColor, fThickness); - pl__add_line(ptDrawlist, atVerticies[3], atVerticies[0], tColor, fThickness); - pl__add_line(ptDrawlist, atVerticies[0], atVerticies[4], tColor, fThickness); - pl__add_line(ptDrawlist, atVerticies[1], atVerticies[5], tColor, fThickness); - pl__add_line(ptDrawlist, atVerticies[2], atVerticies[6], tColor, fThickness); - pl__add_line(ptDrawlist, atVerticies[3], atVerticies[7], tColor, fThickness); - pl__add_line(ptDrawlist, atVerticies[4], atVerticies[5], tColor, fThickness); - pl__add_line(ptDrawlist, atVerticies[5], atVerticies[6], tColor, fThickness); - pl__add_line(ptDrawlist, atVerticies[6], atVerticies[7], tColor, fThickness); - pl__add_line(ptDrawlist, atVerticies[7], atVerticies[4], tColor, fThickness); -} - -static void -pl__add_aabb(plDrawList3D* ptDrawlist, plVec3 tMin, plVec3 tMax, plVec4 tColor, float fThickness) -{ - - const plVec3 atVerticies[] = { - { tMin.x, tMin.y, tMin.z }, - { tMax.x, tMin.y, tMin.z }, - { tMax.x, tMax.y, tMin.z }, - { tMin.x, tMax.y, tMin.z }, - { tMin.x, tMin.y, tMax.z }, - { tMax.x, tMin.y, tMax.z }, - { tMax.x, tMax.y, tMax.z }, - { tMin.x, tMax.y, tMax.z }, - }; - - pl__add_line(ptDrawlist, atVerticies[0], atVerticies[1], tColor, fThickness); - pl__add_line(ptDrawlist, atVerticies[1], atVerticies[2], tColor, fThickness); - pl__add_line(ptDrawlist, atVerticies[2], atVerticies[3], tColor, fThickness); - pl__add_line(ptDrawlist, atVerticies[3], atVerticies[0], tColor, fThickness); - pl__add_line(ptDrawlist, atVerticies[0], atVerticies[4], tColor, fThickness); - pl__add_line(ptDrawlist, atVerticies[1], atVerticies[5], tColor, fThickness); - pl__add_line(ptDrawlist, atVerticies[2], atVerticies[6], tColor, fThickness); - pl__add_line(ptDrawlist, atVerticies[3], atVerticies[7], tColor, fThickness); - pl__add_line(ptDrawlist, atVerticies[4], atVerticies[5], tColor, fThickness); - pl__add_line(ptDrawlist, atVerticies[5], atVerticies[6], tColor, fThickness); - pl__add_line(ptDrawlist, atVerticies[6], atVerticies[7], tColor, fThickness); - pl__add_line(ptDrawlist, atVerticies[7], atVerticies[4], tColor, fThickness); -} - -static void -pl__add_bezier_quad(plDrawList3D* ptDrawlist, plVec3 tP0, plVec3 tP1, plVec3 tP2, plVec4 tColor, float fThickness, uint32_t uSegments) -{ - - // order of the bezier curve inputs are 0=start, 1=control, 2=ending - - if(uSegments == 0) - uSegments = 12; - - // set up first point - plVec3 atVerticies[2] = {(plVec3){0.0, 0.0, 0.0},tP0}; - - for (int i = 1; i < (int)uSegments; i++) - { - const float t = i / (float)uSegments; - const float u = 1.0f - t; - const float tt = t * t; - const float uu = u * u; - - const plVec3 p0 = pl_mul_vec3_scalarf(tP0, uu); - const plVec3 p1 = pl_mul_vec3_scalarf(tP1, (2.0f * u * t)); - const plVec3 p2 = pl_mul_vec3_scalarf(tP2, tt); - const plVec3 p3 = pl_add_vec3(p0,p1); - const plVec3 p4 = pl_add_vec3(p2,p3); - - // shift and add next point - atVerticies[0] = atVerticies[1]; - atVerticies[1] = p4; - - pl__add_line(ptDrawlist, atVerticies[0], atVerticies[1], tColor, fThickness); - } - - // set up last point - atVerticies[0] = atVerticies[1]; - atVerticies[1] = tP2; - pl__add_line(ptDrawlist, atVerticies[0], atVerticies[1], tColor, fThickness); -} - -static void -pl__add_bezier_cubic(plDrawList3D* ptDrawlist, plVec3 tP0, plVec3 tP1, plVec3 tP2, plVec3 tP3, plVec4 tColor, float fThickness, uint32_t uSegments) -{ - // order of the bezier curve inputs are 0=start, 1=control 1, 2=control 2, 3=ending - - if(uSegments == 0) - uSegments = 12; - - // set up first point - plVec3 atVerticies[2] = {(plVec3){0.0, 0.0, 0.0},tP0}; - - for (int i = 1; i < (int)uSegments; i++) - { - const float t = i / (float)uSegments; - const float u = 1.0f - t; - const float tt = t * t; - const float uu = u * u; - const float uuu = uu * u; - const float ttt = tt * t; - - const plVec3 p0 = pl_mul_vec3_scalarf(tP0, uuu); - const plVec3 p1 = pl_mul_vec3_scalarf(tP1, (3.0f * uu * t)); - const plVec3 p2 = pl_mul_vec3_scalarf(tP2, (3.0f * u * tt)); - const plVec3 p3 = pl_mul_vec3_scalarf(tP3, (ttt)); - const plVec3 p5 = pl_add_vec3(p0,p1); - const plVec3 p6 = pl_add_vec3(p2,p3); - const plVec3 p7 = pl_add_vec3(p5,p6); - - // shift and add next point - atVerticies[0] = atVerticies[1]; - atVerticies[1] = p7; - - pl__add_line(ptDrawlist, atVerticies[0], atVerticies[1], tColor, fThickness); - } - - // set up last point - atVerticies[0] = atVerticies[1]; - atVerticies[1] = tP3; - pl__add_line(ptDrawlist, atVerticies[0], atVerticies[1], tColor, fThickness); -} - -//----------------------------------------------------------------------------- -// [SECTION] internal api implementation -//----------------------------------------------------------------------------- - -static plBufferHandle -pl__create_staging_buffer(const plBufferDescription* ptDesc, const char* pcName, uint32_t uIdentifier) -{ - // for convience - plDevice* ptDevice = &gptCtx->ptGraphics->tDevice; - - // create buffer - plTempAllocator tTempAllocator = {0}; - const plBufferHandle tHandle = gptDevice->create_buffer(ptDevice, ptDesc, pl_temp_allocator_sprintf(&tTempAllocator, "%s: %u", pcName, uIdentifier)); - pl_temp_allocator_reset(&tTempAllocator); - - // retrieve new buffer - plBuffer* ptBuffer = gptDevice->get_buffer(ptDevice, tHandle); - - // allocate memory - const plDeviceMemoryAllocation tAllocation = gptCtx->ptStagingUnCachedAllocator->allocate(gptCtx->ptStagingUnCachedAllocator->ptInst, - ptBuffer->tMemoryRequirements.uMemoryTypeBits, - ptBuffer->tMemoryRequirements.ulSize, - ptBuffer->tMemoryRequirements.ulAlignment, - pl_temp_allocator_sprintf(&tTempAllocator, "%s: %u", pcName, uIdentifier)); - - // bind memory - gptDevice->bind_buffer_to_memory(ptDevice, tHandle, &tAllocation); - pl_temp_allocator_free(&tTempAllocator); - return tHandle; -} - -static const plPipelineEntry* -pl__get_pipeline(plRenderPassHandle tRenderPass, uint32_t uMSAASampleCount, pl3DDrawFlags tFlags, uint32_t uSubpassIndex) -{ - // check if pipeline exists - for(uint32_t i = 0; i < pl_sb_size(gptCtx->sbtPipelineEntries); i++) - { - const plPipelineEntry* ptEntry = &gptCtx->sbtPipelineEntries[i]; - if(ptEntry->tRenderPass.uIndex == tRenderPass.uIndex && ptEntry->uMSAASampleCount == uMSAASampleCount && ptEntry->tFlags == tFlags && ptEntry->uSubpassIndex == uSubpassIndex) - { - return ptEntry; - } - } - - pl_sb_add(gptCtx->sbtPipelineEntries); - plPipelineEntry* ptEntry = &gptCtx->sbtPipelineEntries[pl_sb_size(gptCtx->sbtPipelineEntries) - 1]; - ptEntry->tFlags = tFlags; - ptEntry->tRenderPass = tRenderPass; - ptEntry->uMSAASampleCount = uMSAASampleCount; - ptEntry->uSubpassIndex = uSubpassIndex; - - uint64_t ulCullMode = PL_CULL_MODE_NONE; - if(tFlags & PL_3D_DRAW_FLAG_CULL_FRONT) - ulCullMode |= PL_CULL_MODE_CULL_FRONT; - if(tFlags & PL_3D_DRAW_FLAG_CULL_BACK) - ulCullMode |= PL_CULL_MODE_CULL_BACK; - - const plShaderDescription t3DShaderDesc = { - - #ifdef PL_METAL_BACKEND - .pcVertexShader = "../shaders/metal/draw_3d.metal", - .pcPixelShader = "../shaders/metal/draw_3d.metal", - #else - .pcVertexShader = "draw_3d.vert.spv", - .pcPixelShader = "draw_3d.frag.spv", - #endif - .tGraphicsState = { - .ulDepthWriteEnabled = tFlags & PL_3D_DRAW_FLAG_DEPTH_WRITE, - .ulDepthMode = tFlags & PL_3D_DRAW_FLAG_DEPTH_TEST ? PL_COMPARE_MODE_LESS : PL_COMPARE_MODE_ALWAYS, - .ulCullMode = ulCullMode, - .ulWireframe = 0, - .ulStencilMode = PL_COMPARE_MODE_ALWAYS, - .ulStencilRef = 0xff, - .ulStencilMask = 0xff, - .ulStencilOpFail = PL_STENCIL_OP_KEEP, - .ulStencilOpDepthFail = PL_STENCIL_OP_KEEP, - .ulStencilOpPass = PL_STENCIL_OP_KEEP - }, - .tVertexBufferBinding = { - .uByteStride = sizeof(float) * 4, - .atAttributes = { - {.uByteOffset = 0, .tFormat = PL_FORMAT_R32G32B32_FLOAT}, - {.uByteOffset = sizeof(float) * 3, .tFormat = PL_FORMAT_R8G8B8A8_UNORM}, - } - }, - .uConstantCount = 0, - .atBlendStates = { - { - .bBlendEnabled = true, - .tSrcColorFactor = PL_BLEND_FACTOR_SRC_ALPHA, - .tDstColorFactor = PL_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA, - .tColorOp = PL_BLEND_OP_ADD, - .tSrcAlphaFactor = PL_BLEND_FACTOR_SRC_ALPHA, - .tDstAlphaFactor = PL_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA, - .tAlphaOp = PL_BLEND_OP_ADD - } - }, - .uBlendStateCount = 1, - .tRenderPassLayout = gptCtx->ptGraphics->sbtRenderPassesCold[tRenderPass.uIndex].tDesc.tLayout, - .uSubpassIndex = uSubpassIndex, - .uBindGroupLayoutCount = 0, - }; - - const plShaderDescription t3DLineShaderDesc = { - - #ifdef PL_METAL_BACKEND - .pcVertexShader = "../shaders/metal/draw_3d_line.metal", - .pcPixelShader = "../shaders/metal/draw_3d_line.metal", - #else - .pcVertexShader = "draw_3d_line.vert.spv", - .pcPixelShader = "draw_3d.frag.spv", - #endif - .tGraphicsState = { - .ulDepthWriteEnabled = tFlags & PL_3D_DRAW_FLAG_DEPTH_WRITE, - .ulDepthMode = tFlags & PL_3D_DRAW_FLAG_DEPTH_TEST ? PL_COMPARE_MODE_LESS : PL_COMPARE_MODE_ALWAYS, - .ulCullMode = ulCullMode, - .ulWireframe = 0, - .ulStencilMode = PL_COMPARE_MODE_ALWAYS, - .ulStencilRef = 0xff, - .ulStencilMask = 0xff, - .ulStencilOpFail = PL_STENCIL_OP_KEEP, - .ulStencilOpDepthFail = PL_STENCIL_OP_KEEP, - .ulStencilOpPass = PL_STENCIL_OP_KEEP - }, - .tVertexBufferBinding = { - .uByteStride = sizeof(float) * 10, - .atAttributes = { - {.uByteOffset = 0, .tFormat = PL_FORMAT_R32G32B32_FLOAT}, - {.uByteOffset = sizeof(float) * 3, .tFormat = PL_FORMAT_R32G32B32_FLOAT}, - {.uByteOffset = sizeof(float) * 6, .tFormat = PL_FORMAT_R32G32B32_FLOAT}, - {.uByteOffset = sizeof(float) * 9, .tFormat = PL_FORMAT_R8G8B8A8_UNORM}, - } - }, - .uConstantCount = 0, - .atBlendStates = { - { - .bBlendEnabled = true, - .tSrcColorFactor = PL_BLEND_FACTOR_SRC_ALPHA, - .tDstColorFactor = PL_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA, - .tColorOp = PL_BLEND_OP_ADD, - .tSrcAlphaFactor = PL_BLEND_FACTOR_SRC_ALPHA, - .tDstAlphaFactor = PL_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA, - .tAlphaOp = PL_BLEND_OP_ADD - } - }, - .uBlendStateCount = 1, - .tRenderPassLayout = gptCtx->ptGraphics->sbtRenderPassesCold[tRenderPass.uIndex].tDesc.tLayout, - .uSubpassIndex = uSubpassIndex, - .uBindGroupLayoutCount = 0, - }; - - ptEntry->tRegularPipeline = gptDevice->create_shader(&gptCtx->ptGraphics->tDevice, &t3DShaderDesc); - ptEntry->tSecondaryPipeline = gptDevice->create_shader(&gptCtx->ptGraphics->tDevice, &t3DLineShaderDesc); - return ptEntry; -} - -//----------------------------------------------------------------------------- -// [SECTION] extension loading -//----------------------------------------------------------------------------- - -static const plDraw3dI* -pl_load_draw_3d_api(void) -{ - static const plDraw3dI tApi = { - .initialize = pl_initialize, - .cleanup = pl_cleanup, - .request_drawlist = pl_request_drawlist, - .return_drawlist = pl_return_drawlist, - .submit_drawlist = pl__submit_drawlist, - .new_frame = pl_new_draw_3d_frame, - .add_triangle_filled = pl__add_triangle_filled, - .add_line = pl__add_line, - .add_point = pl__add_point, - .add_transform = pl__add_transform, - .add_frustum = pl__add_frustum, - .add_centered_box = pl__add_centered_box, - .add_bezier_quad = pl__add_bezier_quad, - .add_bezier_cubic = pl__add_bezier_cubic, - .add_aabb = pl__add_aabb - }; - return &tApi; -} - -PL_EXPORT void -pl_load_ext(plApiRegistryI* ptApiRegistry, bool bReload) -{ - const plDataRegistryI* ptDataRegistry = ptApiRegistry->first(PL_API_DATA_REGISTRY); - pl_set_memory_context(ptDataRegistry->get_data(PL_CONTEXT_MEMORY)); - - gptDevice = ptApiRegistry->first(PL_API_DEVICE); - gptGfx = ptApiRegistry->first(PL_API_GRAPHICS); - gptGpuAllocators = ptApiRegistry->first(PL_API_GPU_ALLOCATORS); - if(bReload) - { - gptCtx = ptDataRegistry->get_data("plDraw3dContext"); - ptApiRegistry->replace(ptApiRegistry->first(PL_API_DRAW_3D), pl_load_draw_3d_api()); - } - else - { - ptApiRegistry->add(PL_API_DRAW_3D, pl_load_draw_3d_api()); - - static plDraw3dContext tCtx = {0}; - gptCtx = &tCtx; - ptDataRegistry->set_data("plDraw3dContext", gptCtx); - } -} - -PL_EXPORT void -pl_unload_ext(plApiRegistryI* ptApiRegistry) -{ -} \ No newline at end of file diff --git a/extensions/pl_draw_3d_ext.h b/extensions/pl_draw_3d_ext.h deleted file mode 100644 index 2831f633..00000000 --- a/extensions/pl_draw_3d_ext.h +++ /dev/null @@ -1,108 +0,0 @@ -/* - pl_draw_3d_ext.h -*/ - -/* -Index of this file: -// [SECTION] header mess -// [SECTION] defines -// [SECTION] apis -// [SECTION] includes -// [SECTION] forward declarations & basic types -// [SECTION] public api struct -*/ - -//----------------------------------------------------------------------------- -// [SECTION] header mess -//----------------------------------------------------------------------------- - -#ifndef PL_DRAW_3D_EXT_H -#define PL_DRAW_3D_EXT_H - -#define PL_DRAW_3D_EXT_VERSION "0.1.0" -#define PL_DRAW_3D_EXT_VERSION_NUM 000100 - -//----------------------------------------------------------------------------- -// [SECTION] apis -//----------------------------------------------------------------------------- - -#define PL_API_DRAW_3D "PL_API_DRAW_3D" -typedef struct _plDraw3dI plDraw3dI; - -//----------------------------------------------------------------------------- -// [SECTION] defines -//----------------------------------------------------------------------------- - -#ifndef PL_MAX_3D_DRAWLISTS - #define PL_MAX_3D_DRAWLISTS 64 -#endif - -//----------------------------------------------------------------------------- -// [SECTION] includes -//----------------------------------------------------------------------------- - -#include -#include "pl_math.h" - -//----------------------------------------------------------------------------- -// [SECTION] forward declarations & basic types -//----------------------------------------------------------------------------- - -// basic types -typedef struct _plDraw3dContext plDraw3dContext; -typedef struct _plDrawList3D plDrawList3D; -typedef struct _plDrawVertex3DSolid plDrawVertex3DSolid; // single vertex (3D pos + uv + color) -typedef struct _plDrawVertex3DLine plDrawVertex3DLine; // single vertex (pos + uv + color) - -// enums -typedef int pl3DDrawFlags; - -// external -typedef struct _plGraphics plGraphics; // pl_graphics_ext.h -typedef struct _plRenderEncoder plRenderEncoder; // pl_graphics_ext.h - -//----------------------------------------------------------------------------- -// [SECTION] enums -//----------------------------------------------------------------------------- - -enum _pl3DDrawFlags -{ - PL_3D_DRAW_FLAG_NONE = 0, - PL_3D_DRAW_FLAG_DEPTH_TEST = 1 << 0, - PL_3D_DRAW_FLAG_DEPTH_WRITE = 1 << 1, - PL_3D_DRAW_FLAG_CULL_FRONT = 1 << 2, - PL_3D_DRAW_FLAG_CULL_BACK = 1 << 3, - PL_3D_DRAW_FLAG_FRONT_FACE_CW = 1 << 4, -}; - -//----------------------------------------------------------------------------- -// [SECTION] public api struct -//----------------------------------------------------------------------------- - -typedef struct _plDraw3dI -{ - // init/cleanup - void (*initialize)(plGraphics*); - void (*cleanup) (void); - - // setup - plDrawList3D* (*request_drawlist)(void); - void (*return_drawlist)(plDrawList3D*); - - // per frame - void (*new_frame)(void); - void (*submit_drawlist)(plDrawList3D*, plRenderEncoder, float fWidth, float fHeight, const plMat4* ptMVP, pl3DDrawFlags, uint32_t uMSAASampleCount); - - // drawing - void (*add_triangle_filled)(plDrawList3D*, plVec3 tP0, plVec3 tP1, plVec3 tP2, plVec4 tColor); - void (*add_line) (plDrawList3D*, plVec3 tP0, plVec3 tP1, plVec4 tColor, float fThickness); - void (*add_point) (plDrawList3D*, plVec3 tP0, plVec4 tColor, float fLength, float fThickness); - void (*add_transform) (plDrawList3D*, const plMat4* ptTransform, float fLength, float fThickness); - void (*add_frustum) (plDrawList3D*, const plMat4* ptTransform, float fYFov, float fAspect, float fNearZ, float fFarZ, plVec4 tColor, float fThickness); - void (*add_centered_box) (plDrawList3D*, plVec3 tCenter, float fWidth, float fHeight, float fDepth, plVec4 tColor, float fThickness); - void (*add_aabb) (plDrawList3D*, plVec3 tMin, plVec3 tMax, plVec4 tColor, float fThickness); - void (*add_bezier_quad) (plDrawList3D*, plVec3 tP0, plVec3 tP1, plVec3 tP2, plVec4 tColor, float fThickness, uint32_t uSegments); - void (*add_bezier_cubic) (plDrawList3D*, plVec3 tP0, plVec3 tP1, plVec3 tP2, plVec3 tP3, plVec4 tColor, float fThickness, uint32_t uSegments); -} plDraw3dI; - -#endif // PL_DRAW_3D_EXT_H \ No newline at end of file diff --git a/extensions/pl_draw_ext.c b/extensions/pl_draw_ext.c new file mode 100644 index 00000000..b480bde9 --- /dev/null +++ b/extensions/pl_draw_ext.c @@ -0,0 +1,3301 @@ +/* + pl_draw_3d_ext.c +*/ + +/* +Index of this file: +// [SECTION] includes +// [SECTION] internal structs +// [SECTION] global data +// [SECTION] required apis +// [SECTION] internal api +// [SECTION] public api implementation +// [SECTION] internal api implementation +// [SECTION] extension loading +*/ + +//----------------------------------------------------------------------------- +// [SECTION] includes +//----------------------------------------------------------------------------- + +#include +#include "pilotlight.h" +#define PL_MATH_INCLUDE_FUNCTIONS +#include "pl_draw_ext.h" +#include "pl_ds.h" +#include "pl_memory.h" +#include "pl_string.h" + +// extensions +#include "pl_graphics_ext.h" + +// stb libs +#include "stb_rect_pack.h" +#include "stb_truetype.h" + +//----------------------------------------------------------------------------- +// [SECTION] internal structs +//----------------------------------------------------------------------------- + +typedef struct _plFontPrepData +{ + stbtt_fontinfo fontInfo; + stbtt_pack_range* ranges; + stbrp_rect* rects; + unsigned char* ptrTtf; + uint32_t uTotalCharCount; + float scale; + uint32_t area; +} plFontPrepData; + +typedef struct _plDrawVertex3DSolid +{ + float afPos[3]; + uint32_t uColor; +} plDrawVertex3DSolid; + +typedef struct _plDrawVertex3DLine +{ + float afPos[3]; + float fDirection; + float fThickness; + float fMultiply; + float afPosOther[3]; + uint32_t uColor; +} plDrawVertex3DLine; + +typedef struct _plDrawList3D +{ + plDrawVertex3DSolid* sbtSolidVertexBuffer; + uint32_t* sbtSolidIndexBuffer; + plDrawVertex3DLine* sbtLineVertexBuffer; + uint32_t* sbtLineIndexBuffer; +} plDrawList3D; + +typedef struct _plDrawVertex +{ + float afPos[2]; + float afUv[2]; + uint32_t uColor; +} plDrawVertex; + +typedef struct _plDrawCommand +{ + uint32_t uVertexOffset; + uint32_t uIndexOffset; + uint32_t uElementCount; + plTextureHandle tTextureId; + plRect tClip; + bool bSdf; +} plDrawCommand; + + +typedef struct _plDrawLayer2D +{ + const char* pcName; + plDrawList2D* ptDrawlist; + plDrawCommand* sbtCommandBuffer; + uint32_t* sbuIndexBuffer; + plVec2* sbtPath; + uint32_t uVertexCount; + plDrawCommand* _ptLastCommand; +} plDrawLayer2D; + +typedef struct _plDrawList2D +{ + plDrawLayer2D** sbtSubmittedLayers; + plDrawLayer2D** sbtLayerCache; + plDrawLayer2D** sbtLayersCreated; + plDrawCommand* sbtDrawCommands; + plDrawVertex* sbtVertexBuffer; + uint32_t uIndexBufferByteSize; + uint32_t uLayersCreated; + plRect* sbtClipStack; + int _padding; +} plDrawList2D; + +typedef struct _plPipelineEntry +{ + plRenderPassHandle tRenderPass; + uint32_t uMSAASampleCount; + plShaderHandle tRegularPipeline; + plShaderHandle tSecondaryPipeline; + plDrawFlags tFlags; + uint32_t uSubpassIndex; +} plPipelineEntry; + +typedef struct _plBufferInfo +{ + // vertex buffer + plBufferHandle tVertexBuffer; + uint32_t uVertexBufferSize; + uint32_t uVertexBufferOffset; + + // index buffer + +} plBufferInfo; + +typedef struct _plDrawContext +{ + plGraphics* ptGraphics; + plPipelineEntry* sbt3dPipelineEntries; + plPipelineEntry* sbt2dPipelineEntries; + + // 2D resources + plPoolAllocator tDrawlistPool2D; + char acPoolBuffer2D[sizeof(plDrawList2D) * (PL_MAX_DRAWLISTS + 1)]; + plDrawList2D* aptDrawlists2D[PL_MAX_DRAWLISTS]; + plBufferInfo atBufferInfo[PL_FRAMES_IN_FLIGHT]; + uint32_t uDrawlistCount2D; + plSamplerHandle tFontSampler; + plBindGroupHandle tFontSamplerBindGroup; + + // 3D resources + plPoolAllocator tDrawlistPool3D; + char acPoolBuffer3D[sizeof(plDrawList3D) * (PL_MAX_DRAWLISTS + 1)]; + plDrawList3D* aptDrawlists3D[PL_MAX_DRAWLISTS]; + uint32_t uDrawlistCount3D; + plBufferInfo at3DBufferInfo[PL_FRAMES_IN_FLIGHT]; + plBufferInfo atLineBufferInfo[PL_FRAMES_IN_FLIGHT]; + + // shared resources + plBufferHandle atIndexBuffer[PL_FRAMES_IN_FLIGHT]; + uint32_t auIndexBufferSize[PL_FRAMES_IN_FLIGHT]; + uint32_t auIndexBufferOffset[PL_FRAMES_IN_FLIGHT]; + + // font + plFontAtlas* ptFontAtlas; + +} plDrawContext; + +//----------------------------------------------------------------------------- +// [SECTION] global data +//----------------------------------------------------------------------------- + +static plDrawContext* gptCtx = NULL; + +//----------------------------------------------------------------------------- +// [SECTION] required apis +//----------------------------------------------------------------------------- + +static const plDeviceI* gptDevice = NULL; +static const plGraphicsI* gptGfx = NULL; + +//----------------------------------------------------------------------------- +// [SECTION] internal api +//----------------------------------------------------------------------------- + +static plBufferHandle pl__create_staging_buffer(const plBufferDescription*, const char* pcName, uint32_t uIdentifier); +static const plPipelineEntry* pl__get_3d_pipeline (plRenderPassHandle, uint32_t uMSAASampleCount, plDrawFlags, uint32_t uSubpassIndex); +static const plPipelineEntry* pl__get_2d_pipeline (plRenderPassHandle, uint32_t uMSAASampleCount, uint32_t uSubpassIndex); + +static void pl__prepare_draw_command(plDrawLayer2D*, plTextureHandle texture, bool sdf); +static void pl__reserve_triangles(plDrawLayer2D*, uint32_t indexCount, uint32_t uVertexCount); +static void pl__add_vertex(plDrawLayer2D*, plVec2 pos, plVec4 color, plVec2 uv); +static void pl__add_index(plDrawLayer2D*, uint32_t vertexStart, uint32_t i0, uint32_t i1, uint32_t i2); +static inline float pl__get_max(float v1, float v2) { return v1 > v2 ? v1 : v2;} +static inline int pl__get_min(int v1, int v2) { return v1 < v2 ? v1 : v2;} +static char* plu__read_file(const char* file); + +// math +#define pl__add_vec2(left, right) (plVec2){(left).x + (right).x, (left).y + (right).y} +#define pl__subtract_vec2(left, right) (plVec2){(left).x - (right).x, (left).y - (right).y} +#define pl__mul_vec2_f(left, right) (plVec2){(left).x * (right), (left).y * (right)} +#define pl__mul_f_vec2(left, right) (plVec2){(left) * (right).x, (left) * (right).y} + +// stateful drawing +#define pl__submit_path(ptLayer, color, thickness)\ + pl_add_lines((ptLayer), (ptLayer)->sbtPath, pl_sb_size((ptLayer)->sbtPath) - 1, (color), (thickness));\ + pl_sb_reset((ptLayer)->sbtPath); + +#define PL_NORMALIZE2F_OVER_ZERO(VX,VY) \ + { float d2 = (VX) * (VX) + (VY) * (VY); \ + if (d2 > 0.0f) { float inv_len = 1.0f / sqrtf(d2); (VX) *= inv_len; (VY) *= inv_len; } } (void)0 + + +//----------------------------------------------------------------------------- +// [SECTION] public api implementation +//----------------------------------------------------------------------------- + +static void +pl_initialize(plGraphics* ptGraphics) +{ + gptCtx->ptGraphics = ptGraphics; + + pl_sb_reserve(gptCtx->sbt3dPipelineEntries, 32); + pl_sb_reserve(gptCtx->sbt2dPipelineEntries, 32); + + // create initial buffers + const plBufferDescription tIndexBufferDesc = { + .tUsage = PL_BUFFER_USAGE_INDEX | PL_BUFFER_USAGE_STAGING, + .uByteSize = 4096 + }; + + const plBufferDescription tVertexBufferDesc = { + .tUsage = PL_BUFFER_USAGE_VERTEX | PL_BUFFER_USAGE_STAGING, + .uByteSize = 4096 + }; + + for(uint32_t i = 0; i < PL_FRAMES_IN_FLIGHT; i++) + { + gptCtx->auIndexBufferSize[i] = 4096; + gptCtx->atBufferInfo[i].uVertexBufferSize = 4096; + gptCtx->at3DBufferInfo[i].uVertexBufferSize = 4096; + gptCtx->atLineBufferInfo[i].uVertexBufferSize = 4096; + gptCtx->atIndexBuffer[i] = pl__create_staging_buffer(&tIndexBufferDesc, "draw idx buffer", i); + gptCtx->atBufferInfo[i].tVertexBuffer= pl__create_staging_buffer(&tVertexBufferDesc, "draw vtx buffer", i); + gptCtx->at3DBufferInfo[i].tVertexBuffer= pl__create_staging_buffer(&tVertexBufferDesc, "3d draw vtx buffer", i); + gptCtx->atLineBufferInfo[i].tVertexBuffer= pl__create_staging_buffer(&tVertexBufferDesc, "3d line draw vtx buffer", i); + } + + size_t szBufferSize = sizeof(plDrawList3D) * (PL_MAX_DRAWLISTS + 1); + pl_pool_allocator_init(&gptCtx->tDrawlistPool3D, PL_MAX_DRAWLISTS, sizeof(plDrawList3D), 0, &szBufferSize, gptCtx->acPoolBuffer3D); + + szBufferSize = sizeof(plDrawList2D) * (PL_MAX_DRAWLISTS + 1); + pl_pool_allocator_init(&gptCtx->tDrawlistPool2D, PL_MAX_DRAWLISTS, sizeof(plDrawList2D), 0, &szBufferSize, gptCtx->acPoolBuffer2D); + + // 2d + const plSamplerDesc tSamplerDesc = { + .tFilter = PL_FILTER_LINEAR, + .fMinMip = 0.0f, + .fMaxMip = 64.0f, + .fMaxAnisotropy = 1.0f, + .tVerticalWrap = PL_WRAP_MODE_CLAMP, + .tHorizontalWrap = PL_WRAP_MODE_CLAMP, + .tMipmapMode = PL_MIPMAP_MODE_LINEAR + }; + gptCtx->tFontSampler = gptDevice->create_sampler(&ptGraphics->tDevice, &tSamplerDesc, "font sampler"); + + const plBindGroupLayout tSamplerBindGroupLayout = { + .uSamplerBindingCount = 1, + .atSamplerBindings = { + {.uSlot = 0, .tStages = PL_STAGE_PIXEL} + } + }; + gptCtx->tFontSamplerBindGroup = gptDevice->create_bind_group(&ptGraphics->tDevice, &tSamplerBindGroupLayout, "font sampler bindgroup"); + const plBindGroupUpdateSamplerData atSamplerData[] = { + { .uSlot = 0, .tSampler = gptCtx->tFontSampler} + }; + + plBindGroupUpdateData tBGData0 = { + .uSamplerCount = 1, + .atSamplerBindings = atSamplerData, + }; + gptDevice->update_bind_group(&ptGraphics->tDevice, gptCtx->tFontSamplerBindGroup, &tBGData0); +} + +static void +pl_cleanup(void) +{ + + for(uint32_t i = 0; i < PL_FRAMES_IN_FLIGHT; i++) + { + gptDevice->destroy_buffer(&gptCtx->ptGraphics->tDevice, gptCtx->atBufferInfo[i].tVertexBuffer); + gptDevice->destroy_buffer(&gptCtx->ptGraphics->tDevice, gptCtx->at3DBufferInfo[i].tVertexBuffer); + gptDevice->destroy_buffer(&gptCtx->ptGraphics->tDevice, gptCtx->atLineBufferInfo[i].tVertexBuffer); + gptDevice->destroy_buffer(&gptCtx->ptGraphics->tDevice, gptCtx->atIndexBuffer[i]); + gptDevice->destroy_buffer(&gptCtx->ptGraphics->tDevice, gptCtx->atIndexBuffer[i]); + } + + for(uint32_t i = 0; i < gptCtx->uDrawlistCount3D; i++) + { + plDrawList3D* ptDrawlist = gptCtx->aptDrawlists3D[i]; + pl_sb_free(ptDrawlist->sbtSolidIndexBuffer); + pl_sb_free(ptDrawlist->sbtSolidVertexBuffer); + pl_sb_free(ptDrawlist->sbtLineVertexBuffer); + pl_sb_free(ptDrawlist->sbtLineIndexBuffer); + } + for(uint32_t i = 0; i < gptCtx->uDrawlistCount2D; i++) + { + plDrawList2D* ptDrawlist = gptCtx->aptDrawlists2D[i]; + pl_sb_free(ptDrawlist->sbtClipStack); + pl_sb_free(ptDrawlist->sbtDrawCommands); + pl_sb_free(ptDrawlist->sbtLayerCache); + + pl_sb_free(ptDrawlist->sbtSubmittedLayers); + pl_sb_free(ptDrawlist->sbtVertexBuffer); + + for(uint32_t j = 0; j < pl_sb_size(ptDrawlist->sbtLayersCreated); j++) + { + pl_sb_free(ptDrawlist->sbtLayersCreated[j]->sbtCommandBuffer); + pl_sb_free(ptDrawlist->sbtLayersCreated[j]->sbtPath); + pl_sb_free(ptDrawlist->sbtLayersCreated[j]->sbuIndexBuffer); + PL_FREE(ptDrawlist->sbtLayersCreated[j]); + } + pl_sb_free(ptDrawlist->sbtLayersCreated); + } + pl_sb_free(gptCtx->sbt3dPipelineEntries); + pl_sb_free(gptCtx->sbt2dPipelineEntries); +} + +static plDrawList3D* +pl_request_3d_drawlist(void) +{ + plDrawList3D* ptDrawlist = pl_pool_allocator_alloc(&gptCtx->tDrawlistPool3D); + PL_ASSERT(ptDrawlist && "no drawlist available"); + + pl_sb_reserve(ptDrawlist->sbtLineIndexBuffer, 1024); + pl_sb_reserve(ptDrawlist->sbtLineVertexBuffer, 1024); + pl_sb_reserve(ptDrawlist->sbtSolidIndexBuffer, 1024); + pl_sb_reserve(ptDrawlist->sbtSolidVertexBuffer, 1024); + + gptCtx->aptDrawlists3D[gptCtx->uDrawlistCount3D] = ptDrawlist; + gptCtx->uDrawlistCount3D++; + return ptDrawlist; +} + +static plDrawList2D* +pl_request_2d_drawlist(void) +{ + plDrawList2D* ptDrawlist = pl_pool_allocator_alloc(&gptCtx->tDrawlistPool2D); + PL_ASSERT(ptDrawlist && "no drawlist available"); + + pl_sb_reserve(ptDrawlist->sbtVertexBuffer, 1024); + + gptCtx->aptDrawlists2D[gptCtx->uDrawlistCount2D] = ptDrawlist; + gptCtx->uDrawlistCount2D++; + return ptDrawlist; +} + +static void +pl_return_3d_drawlist(plDrawList3D* ptDrawlist) +{ + + pl_sb_free(ptDrawlist->sbtLineIndexBuffer); + pl_sb_free(ptDrawlist->sbtLineVertexBuffer); + pl_sb_free(ptDrawlist->sbtSolidIndexBuffer); + pl_sb_free(ptDrawlist->sbtSolidVertexBuffer); + + uint32_t uCurrentIndex = 0; + for(uint32_t i = 0; i < gptCtx->uDrawlistCount3D; i++) + { + if(gptCtx->aptDrawlists3D[i] != ptDrawlist) // skip returning drawlist + { + plDrawList3D* ptCurrentDrawlist = gptCtx->aptDrawlists3D[i]; + gptCtx->aptDrawlists3D[uCurrentIndex] = ptCurrentDrawlist; + uCurrentIndex++; + } + } + pl_pool_allocator_free(&gptCtx->tDrawlistPool3D, ptDrawlist); + gptCtx->uDrawlistCount3D--; +} + +static void +pl_return_2d_drawlist(plDrawList2D* ptDrawlist) +{ + pl_sb_free(ptDrawlist->sbtVertexBuffer); + + uint32_t uCurrentIndex = 0; + for(uint32_t i = 0; i < gptCtx->uDrawlistCount2D; i++) + { + if(gptCtx->aptDrawlists2D[i] != ptDrawlist) // skip returning drawlist + { + plDrawList2D* ptCurrentDrawlist = gptCtx->aptDrawlists2D[i]; + gptCtx->aptDrawlists2D[uCurrentIndex] = ptCurrentDrawlist; + uCurrentIndex++; + } + } + pl_pool_allocator_free(&gptCtx->tDrawlistPool2D, ptDrawlist); + gptCtx->uDrawlistCount2D--; +} + +static plDrawLayer2D* +pl_request_2d_layer(plDrawList2D* ptDrawlist, const char* pcName) +{ + plDrawLayer2D* ptLayer = NULL; + + // check if ptDrawlist has any cached layers + // which reduces allocations necessary since + // cached layers' buffers are only reset + if(pl_sb_size(ptDrawlist->sbtLayerCache) > 0) + { + ptLayer = pl_sb_pop(ptDrawlist->sbtLayerCache); + } + + else // create new ptLayer + { + ptDrawlist->uLayersCreated++; + ptLayer = PL_ALLOC(sizeof(plDrawLayer2D)); + memset(ptLayer, 0, sizeof(plDrawLayer2D)); + ptLayer->ptDrawlist = ptDrawlist; + pl_sb_push(ptDrawlist->sbtLayersCreated, ptLayer); + } + + ptLayer->pcName = pcName; + pl_sb_reserve(ptLayer->sbuIndexBuffer, 1024); + + return ptLayer; +} + +static void +pl_return_2d_layer(plDrawLayer2D* ptLayer) +{ + ptLayer->pcName = ""; + ptLayer->_ptLastCommand = NULL; + ptLayer->uVertexCount = 0u; + pl_sb_reset(ptLayer->sbtCommandBuffer); + pl_sb_reset(ptLayer->sbuIndexBuffer); + pl_sb_reset(ptLayer->sbtPath); + pl_sb_push(ptLayer->ptDrawlist->sbtLayerCache, ptLayer); +} + +static void +pl_submit_2d_layer(plDrawLayer2D* ptLayer) +{ + pl_sb_push(ptLayer->ptDrawlist->sbtSubmittedLayers, ptLayer); + ptLayer->ptDrawlist->uIndexBufferByteSize += pl_sb_size(ptLayer->sbuIndexBuffer) * sizeof(uint32_t); +} + +static void +pl_add_lines(plDrawLayer2D* ptLayer, plVec2* atPoints, uint32_t count, plVec4 color, float thickness) +{ + pl__prepare_draw_command(ptLayer, gptCtx->ptFontAtlas->tTexture, false); + pl__reserve_triangles(ptLayer, 6 * count, 4 * count); + + for(uint32_t i = 0u; i < count; i++) + { + float dx = atPoints[i + 1].x - atPoints[i].x; + float dy = atPoints[i + 1].y - atPoints[i].y; + PL_NORMALIZE2F_OVER_ZERO(dx, dy); + + plVec2 normalVector = + { + .x = dy, + .y = -dx + }; + + plVec2 cornerPoints[4] = + { + pl__subtract_vec2(atPoints[i], pl__mul_vec2_f(normalVector, thickness / 2.0f)), + pl__subtract_vec2(atPoints[i + 1], pl__mul_vec2_f(normalVector, thickness / 2.0f)), + pl__add_vec2( atPoints[i + 1], pl__mul_vec2_f(normalVector, thickness / 2.0f)), + pl__add_vec2( atPoints[i], pl__mul_vec2_f(normalVector, thickness / 2.0f)) + }; + + uint32_t vertexStart = pl_sb_size(ptLayer->ptDrawlist->sbtVertexBuffer); + pl__add_vertex(ptLayer, cornerPoints[0], color, (plVec2){gptCtx->ptFontAtlas->afWhiteUv[0], gptCtx->ptFontAtlas->afWhiteUv[1]}); + pl__add_vertex(ptLayer, cornerPoints[1], color, (plVec2){gptCtx->ptFontAtlas->afWhiteUv[0], gptCtx->ptFontAtlas->afWhiteUv[1]}); + pl__add_vertex(ptLayer, cornerPoints[2], color, (plVec2){gptCtx->ptFontAtlas->afWhiteUv[0], gptCtx->ptFontAtlas->afWhiteUv[1]}); + pl__add_vertex(ptLayer, cornerPoints[3], color, (plVec2){gptCtx->ptFontAtlas->afWhiteUv[0], gptCtx->ptFontAtlas->afWhiteUv[1]}); + + pl__add_index(ptLayer, vertexStart, 0, 1, 2); + pl__add_index(ptLayer, vertexStart, 0, 2, 3); + } +} + +static void +pl_add_line(plDrawLayer2D* ptLayer, plVec2 p0, plVec2 p1, plVec4 tColor, float fThickness) +{ + pl_sb_push(ptLayer->sbtPath, p0); + pl_sb_push(ptLayer->sbtPath, p1); + pl__submit_path(ptLayer, tColor, fThickness); +} + +static void +pl_add_text_ex(plDrawLayer2D* ptLayer, plFont* font, float size, plVec2 p, plVec4 color, const char* text, const char* pcTextEnd, float wrap) +{ + float scale = size > 0.0f ? size / font->tConfig.fFontSize : 1.0f; + + float fLineSpacing = scale * font->fLineSpacing; + const plVec2 originalPosition = p; + bool firstCharacter = true; + + while(text < pcTextEnd) + { + uint32_t c = (uint32_t)*text; + if(c < 0x80) + text += 1; + else + { + text += pl_text_char_from_utf8(&c, text, NULL); + if(c == 0) // malformed UTF-8? + break; + } + + if(c == '\n') + { + p.x = originalPosition.x; + p.y += fLineSpacing; + } + else if(c == '\r') + { + // do nothing + } + else + { + + bool glyphFound = false; + for(uint32_t i = 0u; i < pl_sb_size(font->tConfig.sbtRanges); i++) + { + if (c >= (uint32_t)font->tConfig.sbtRanges[i].iFirstCodePoint && c < (uint32_t)font->tConfig.sbtRanges[i].iFirstCodePoint + (uint32_t)font->tConfig.sbtRanges[i].uCharCount) + { + + + float x0,y0,s0,t0; // top-left + float x1,y1,s1,t1; // bottom-right + + const plFontGlyph* glyph = &font->sbtGlyphs[font->sbuCodePoints[c]]; + + // adjust for left side bearing if first char + if(firstCharacter) + { + if(glyph->leftBearing > 0.0f) p.x += glyph->leftBearing * scale; + firstCharacter = false; + } + + x0 = p.x + glyph->x0 * scale; + x1 = p.x + glyph->x1 * scale; + y0 = p.y + glyph->y0 * scale; + y1 = p.y + glyph->y1 * scale; + + if(wrap > 0.0f && x1 > originalPosition.x + wrap) + { + x0 = originalPosition.x + glyph->x0 * scale; + y0 = y0 + fLineSpacing; + x1 = originalPosition.x + glyph->x1 * scale; + y1 = y1 + fLineSpacing; + + p.x = originalPosition.x; + p.y += fLineSpacing; + } + s0 = glyph->u0; + t0 = glyph->v0; + s1 = glyph->u1; + t1 = glyph->v1; + + p.x += glyph->xAdvance * scale; + if(c != ' ') + { + pl__prepare_draw_command(ptLayer, font->ptParentAtlas->tTexture, font->tConfig.bSdf); + pl__reserve_triangles(ptLayer, 6, 4); + uint32_t uVtxStart = pl_sb_size(ptLayer->ptDrawlist->sbtVertexBuffer); + pl__add_vertex(ptLayer, (plVec2){x0, y0}, color, (plVec2){s0, t0}); + pl__add_vertex(ptLayer, (plVec2){x1, y0}, color, (plVec2){s1, t0}); + pl__add_vertex(ptLayer, (plVec2){x1, y1}, color, (plVec2){s1, t1}); + pl__add_vertex(ptLayer, (plVec2){x0, y1}, color, (plVec2){s0, t1}); + + pl__add_index(ptLayer, uVtxStart, 1, 0, 2); + pl__add_index(ptLayer, uVtxStart, 2, 0, 3); + } + + glyphFound = true; + break; + } + } + + PL_ASSERT(glyphFound && "Glyph not found"); + } + } +} + +static void +pl_add_text(plDrawLayer2D* ptLayer, plFont* font, float size, plVec2 p, plVec4 color, const char* text, float wrap) +{ + const char* pcTextEnd = text + strlen(text); + pl_add_text_ex(ptLayer, font, size, p, color, text, pcTextEnd, wrap); +} + +static void +pl_add_text_clipped_ex(plDrawLayer2D* ptLayer, plFont* font, float size, plVec2 p, plVec2 tMin, plVec2 tMax, plVec4 color, const char* text, const char* pcTextEnd, float wrap) +{ + // const plVec2 tTextSize = pl_calculate_text_size_ex(font, size, text, pcTextEnd, wrap); + const plRect tClipRect = {tMin, tMax}; + + float scale = size > 0.0f ? size / font->tConfig.fFontSize : 1.0f; + + float fLineSpacing = scale * font->fLineSpacing; + const plVec2 originalPosition = p; + bool firstCharacter = true; + + while(text < pcTextEnd) + { + uint32_t c = (uint32_t)*text; + if(c < 0x80) + text += 1; + else + { + text += pl_text_char_from_utf8(&c, text, NULL); + if(c == 0) // malformed UTF-8? + break; + } + + if(c == '\n') + { + p.x = originalPosition.x; + p.y += fLineSpacing; + } + else if(c == '\r') + { + // do nothing + } + else + { + + bool glyphFound = false; + for(uint32_t i = 0u; i < pl_sb_size(font->tConfig.sbtRanges); i++) + { + if (c >= (uint32_t)font->tConfig.sbtRanges[i].iFirstCodePoint && c < (uint32_t)font->tConfig.sbtRanges[i].iFirstCodePoint + (uint32_t)font->tConfig.sbtRanges[i].uCharCount) + { + + + float x0,y0,s0,t0; // top-left + float x1,y1,s1,t1; // bottom-right + + const plFontGlyph* glyph = &font->sbtGlyphs[font->sbuCodePoints[c]]; + + // adjust for left side bearing if first char + if(firstCharacter) + { + if(glyph->leftBearing > 0.0f) p.x += glyph->leftBearing * scale; + firstCharacter = false; + } + + x0 = p.x + glyph->x0 * scale; + x1 = p.x + glyph->x1 * scale; + y0 = p.y + glyph->y0 * scale; + y1 = p.y + glyph->y1 * scale; + + if(wrap > 0.0f && x1 > originalPosition.x + wrap) + { + x0 = originalPosition.x + glyph->x0 * scale; + y0 = y0 + fLineSpacing; + x1 = originalPosition.x + glyph->x1 * scale; + y1 = y1 + fLineSpacing; + + p.x = originalPosition.x; + p.y += fLineSpacing; + } + s0 = glyph->u0; + t0 = glyph->v0; + s1 = glyph->u1; + t1 = glyph->v1; + + p.x += glyph->xAdvance * scale; + if(c != ' ' && pl_rect_contains_point(&tClipRect, p)) + { + pl__prepare_draw_command(ptLayer, font->ptParentAtlas->tTexture, font->tConfig.bSdf); + pl__reserve_triangles(ptLayer, 6, 4); + uint32_t uVtxStart = pl_sb_size(ptLayer->ptDrawlist->sbtVertexBuffer); + pl__add_vertex(ptLayer, (plVec2){x0, y0}, color, (plVec2){s0, t0}); + pl__add_vertex(ptLayer, (plVec2){x1, y0}, color, (plVec2){s1, t0}); + pl__add_vertex(ptLayer, (plVec2){x1, y1}, color, (plVec2){s1, t1}); + pl__add_vertex(ptLayer, (plVec2){x0, y1}, color, (plVec2){s0, t1}); + + pl__add_index(ptLayer, uVtxStart, 1, 0, 2); + pl__add_index(ptLayer, uVtxStart, 2, 0, 3); + } + + glyphFound = true; + break; + } + } + + PL_ASSERT(glyphFound && "Glyph not found"); + } + } +} + +static void +pl_add_text_clipped(plDrawLayer2D* ptLayer, plFont* ptFont, float fSize, plVec2 tP, plVec2 tMin, plVec2 tMax, plVec4 tColor, const char* pcText, float fWrap) +{ + const char* pcTextEnd = pcText + strlen(pcText); + pl_add_text_clipped_ex(ptLayer, ptFont, fSize, tP, tMin, tMax, tColor, pcText, pcTextEnd, fWrap); +} + +static void +pl_add_triangle(plDrawLayer2D* ptLayer, plVec2 tP0, plVec2 tP1, plVec2 tP2, plVec4 tColor, float fThickness) +{ + pl_sb_push(ptLayer->sbtPath, tP0); + pl_sb_push(ptLayer->sbtPath, tP1); + pl_sb_push(ptLayer->sbtPath, tP2); + pl_sb_push(ptLayer->sbtPath, tP0); + pl__submit_path(ptLayer, tColor, fThickness); +} + +static void +pl_add_triangle_filled(plDrawLayer2D* ptLayer, plVec2 p0, plVec2 p1, plVec2 p2, plVec4 color) +{ + pl__prepare_draw_command(ptLayer, gptCtx->ptFontAtlas->tTexture, false); + pl__reserve_triangles(ptLayer, 3, 3); + + uint32_t vertexStart = pl_sb_size(ptLayer->ptDrawlist->sbtVertexBuffer); + pl__add_vertex(ptLayer, p0, color, (plVec2){gptCtx->ptFontAtlas->afWhiteUv[0], gptCtx->ptFontAtlas->afWhiteUv[1]}); + pl__add_vertex(ptLayer, p1, color, (plVec2){gptCtx->ptFontAtlas->afWhiteUv[0], gptCtx->ptFontAtlas->afWhiteUv[1]}); + pl__add_vertex(ptLayer, p2, color, (plVec2){gptCtx->ptFontAtlas->afWhiteUv[0], gptCtx->ptFontAtlas->afWhiteUv[1]}); + + pl__add_index(ptLayer, vertexStart, 0, 1, 2); +} + +static void +pl_add_rect(plDrawLayer2D* ptLayer, plVec2 tMinP, plVec2 tMaxP, plVec4 tColor, float fThickness) +{ + const plVec2 fBotLeftVec = {tMinP.x, tMaxP.y}; + const plVec2 fTopRightVec = {tMaxP.x, tMinP.y}; + + pl_sb_push(ptLayer->sbtPath, tMinP); + pl_sb_push(ptLayer->sbtPath, fBotLeftVec); + pl_sb_push(ptLayer->sbtPath, tMaxP); + pl_sb_push(ptLayer->sbtPath, fTopRightVec); + pl_sb_push(ptLayer->sbtPath, tMinP); + pl__submit_path(ptLayer, tColor, fThickness); +} + +static void +pl_add_rect_filled(plDrawLayer2D* ptLayer, plVec2 minP, plVec2 maxP, plVec4 color) +{ + pl__prepare_draw_command(ptLayer, gptCtx->ptFontAtlas->tTexture, false); + pl__reserve_triangles(ptLayer, 6, 4); + + const plVec2 bottomLeft = { minP.x, maxP.y }; + const plVec2 topRight = { maxP.x, minP.y }; + + const uint32_t vertexStart = pl_sb_size(ptLayer->ptDrawlist->sbtVertexBuffer); + pl__add_vertex(ptLayer, minP, color, (plVec2){gptCtx->ptFontAtlas->afWhiteUv[0], gptCtx->ptFontAtlas->afWhiteUv[1]}); + pl__add_vertex(ptLayer, bottomLeft, color, (plVec2){gptCtx->ptFontAtlas->afWhiteUv[0], gptCtx->ptFontAtlas->afWhiteUv[1]}); + pl__add_vertex(ptLayer, maxP, color, (plVec2){gptCtx->ptFontAtlas->afWhiteUv[0], gptCtx->ptFontAtlas->afWhiteUv[1]}); + pl__add_vertex(ptLayer, topRight, color, (plVec2){gptCtx->ptFontAtlas->afWhiteUv[0], gptCtx->ptFontAtlas->afWhiteUv[1]}); + + pl__add_index(ptLayer, vertexStart, 0, 1, 2); + pl__add_index(ptLayer, vertexStart, 0, 2, 3); +} + +static void +pl_add_rect_rounded(plDrawLayer2D* ptLayer, plVec2 tMinP, plVec2 tMaxP, plVec4 tColor, float fThickness, float fRadius, uint32_t uSegments) +{ + // segments is the number of segments used to approximate one corner + + if(uSegments == 0){ uSegments = 3; } + const float fIncrement = PL_PI_2 / uSegments; + float fTheta = 0.0f; + + + const plVec2 bottomRightStart = { tMaxP.x, tMaxP.y - fRadius }; + const plVec2 bottomRightInner = { tMaxP.x - fRadius, tMaxP.y - fRadius }; + const plVec2 bottomRightEnd = { tMaxP.x - fRadius, tMaxP.y }; + + const plVec2 bottomLeftStart = { tMinP.x + fRadius, tMaxP.y }; + const plVec2 bottomLeftInner = { tMinP.x + fRadius, tMaxP.y - fRadius }; + const plVec2 bottomLeftEnd = { tMinP.x , tMaxP.y - fRadius}; + + const plVec2 topLeftStart = { tMinP.x, tMinP.y + fRadius }; + const plVec2 topLeftInner = { tMinP.x + fRadius, tMinP.y + fRadius }; + const plVec2 topLeftEnd = { tMinP.x + fRadius, tMinP.y }; + + const plVec2 topRightStart = { tMaxP.x - fRadius, tMinP.y }; + const plVec2 topRightInner = { tMaxP.x - fRadius, tMinP.y + fRadius }; + const plVec2 topRightEnd = { tMaxP.x, tMinP.y + fRadius }; + + pl_sb_push(ptLayer->sbtPath, bottomRightStart); + fTheta += fIncrement; + for(uint32_t i = 1; i < uSegments; i++) + { + pl_sb_push(ptLayer->sbtPath, ((plVec2){bottomRightInner.x + fRadius * sinf(fTheta + PL_PI_2), bottomRightInner.y + fRadius * sinf(fTheta)})); + fTheta += fIncrement; + } + pl_sb_push(ptLayer->sbtPath, bottomRightEnd); + + pl_sb_push(ptLayer->sbtPath, bottomLeftStart); + fTheta += fIncrement; + for(uint32_t i = 1; i < uSegments; i++) + { + pl_sb_push(ptLayer->sbtPath, ((plVec2){bottomLeftInner.x + fRadius * sinf(fTheta + PL_PI_2), bottomLeftInner.y + fRadius * sinf(fTheta)})); + fTheta += fIncrement; + } + pl_sb_push(ptLayer->sbtPath, bottomLeftEnd); + + pl_sb_push(ptLayer->sbtPath, topLeftStart); + fTheta += fIncrement; + for(uint32_t i = 1; i < uSegments; i++) + { + pl_sb_push(ptLayer->sbtPath, ((plVec2){topLeftInner.x + fRadius * sinf(fTheta + PL_PI_2), topLeftInner.y + fRadius * sinf(fTheta)})); + fTheta += fIncrement; + } + pl_sb_push(ptLayer->sbtPath, topLeftEnd); + + pl_sb_push(ptLayer->sbtPath, topRightStart); + fTheta += fIncrement; + for(uint32_t i = 1; i < uSegments; i++) + { + pl_sb_push(ptLayer->sbtPath, ((plVec2){topRightInner.x + fRadius * sinf(fTheta + PL_PI_2), topRightInner.y + fRadius * sinf(fTheta)})); + fTheta += fIncrement; + } + pl_sb_push(ptLayer->sbtPath, topRightEnd); + + pl_sb_push(ptLayer->sbtPath, bottomRightStart); + + pl__submit_path(ptLayer, tColor, fThickness); +} + +static void +pl_add_rect_rounded_filled(plDrawLayer2D* ptLayer, plVec2 tMinP, plVec2 tMaxP, plVec4 tColor, float fRadius, uint32_t uSegments) +{ + if(uSegments == 0){ uSegments = 3; } + const uint32_t numTriangles = (uSegments * 4 + 4); //number segments in midpoint circle, plus square + pl__prepare_draw_command(ptLayer, gptCtx->ptFontAtlas->tTexture, false); + pl__reserve_triangles(ptLayer, numTriangles, numTriangles + 1); + + const uint32_t uVertexStart = pl_sb_size(ptLayer->ptDrawlist->sbtVertexBuffer); + + const float fIncrement = PL_PI_2 / uSegments; + float fTheta = 0.0f; + + const plVec2 bottomRightStart = { tMaxP.x, tMaxP.y - fRadius }; + const plVec2 bottomRightInner = { tMaxP.x - fRadius, tMaxP.y - fRadius }; + const plVec2 bottomRightEnd = { tMaxP.x - fRadius, tMaxP.y }; + + const plVec2 bottomLeftStart = { tMinP.x + fRadius, tMaxP.y }; + const plVec2 bottomLeftInner = { tMinP.x + fRadius, tMaxP.y - fRadius }; + const plVec2 bottomLeftEnd = { tMinP.x , tMaxP.y - fRadius}; + + const plVec2 topLeftStart = { tMinP.x, tMinP.y + fRadius }; + const plVec2 topLeftInner = { tMinP.x + fRadius, tMinP.y + fRadius }; + const plVec2 topLeftEnd = { tMinP.x + fRadius, tMinP.y }; + + const plVec2 topRightStart = { tMaxP.x - fRadius, tMinP.y }; + const plVec2 topRightInner = { tMaxP.x - fRadius, tMinP.y + fRadius }; + const plVec2 topRightEnd = { tMaxP.x, tMinP.y + fRadius }; + + const plVec2 midPoint = {(tMaxP.x-tMinP.x)/2 + tMinP.x, (tMaxP.y-tMinP.y)/2 + tMinP.y}; + pl__add_vertex(ptLayer, midPoint, tColor, (plVec2){gptCtx->ptFontAtlas->afWhiteUv[0], gptCtx->ptFontAtlas->afWhiteUv[1]}); + + pl__add_vertex(ptLayer, bottomRightStart, tColor, (plVec2){gptCtx->ptFontAtlas->afWhiteUv[0], gptCtx->ptFontAtlas->afWhiteUv[1]}); + fTheta += fIncrement; + for(uint32_t i = 1; i < uSegments; i++) + { + pl__add_vertex(ptLayer, ((plVec2){bottomRightInner.x + fRadius * sinf(fTheta + PL_PI_2), bottomRightInner.y + fRadius * sinf(fTheta)}), tColor, (plVec2){gptCtx->ptFontAtlas->afWhiteUv[0], gptCtx->ptFontAtlas->afWhiteUv[1]}); + fTheta += fIncrement; + } + pl__add_vertex(ptLayer, bottomRightEnd, tColor, (plVec2){gptCtx->ptFontAtlas->afWhiteUv[0], gptCtx->ptFontAtlas->afWhiteUv[1]}); + + pl__add_vertex(ptLayer, bottomLeftStart, tColor, (plVec2){gptCtx->ptFontAtlas->afWhiteUv[0], gptCtx->ptFontAtlas->afWhiteUv[1]}); + fTheta += fIncrement; + for(uint32_t i = 1; i < uSegments; i++) + { + pl__add_vertex(ptLayer, ((plVec2){bottomLeftInner.x + fRadius * sinf(fTheta + PL_PI_2), bottomLeftInner.y + fRadius * sinf(fTheta)}), tColor, (plVec2){gptCtx->ptFontAtlas->afWhiteUv[0], gptCtx->ptFontAtlas->afWhiteUv[1]}); + fTheta += fIncrement; + } + pl__add_vertex(ptLayer, bottomLeftEnd, tColor, (plVec2){gptCtx->ptFontAtlas->afWhiteUv[0], gptCtx->ptFontAtlas->afWhiteUv[1]}); + + pl__add_vertex(ptLayer, topLeftStart, tColor, (plVec2){gptCtx->ptFontAtlas->afWhiteUv[0], gptCtx->ptFontAtlas->afWhiteUv[1]}); + fTheta += fIncrement; + for(uint32_t i = 1; i < uSegments; i++) + { + pl__add_vertex(ptLayer, ((plVec2){topLeftInner.x + fRadius * sinf(fTheta + PL_PI_2), topLeftInner.y + fRadius * sinf(fTheta)}), tColor, (plVec2){gptCtx->ptFontAtlas->afWhiteUv[0], gptCtx->ptFontAtlas->afWhiteUv[1]}); + fTheta += fIncrement; + } + pl__add_vertex(ptLayer, topLeftEnd, tColor, (plVec2){gptCtx->ptFontAtlas->afWhiteUv[0], gptCtx->ptFontAtlas->afWhiteUv[1]}); + + pl__add_vertex(ptLayer, topRightStart, tColor, (plVec2){gptCtx->ptFontAtlas->afWhiteUv[0], gptCtx->ptFontAtlas->afWhiteUv[1]}); + fTheta += fIncrement; + for(uint32_t i = 1; i < uSegments; i++) + { + pl__add_vertex(ptLayer, ((plVec2){topRightInner.x + fRadius * sinf(fTheta + PL_PI_2), topRightInner.y + fRadius * sinf(fTheta)}), tColor, (plVec2){gptCtx->ptFontAtlas->afWhiteUv[0], gptCtx->ptFontAtlas->afWhiteUv[1]}); + fTheta += fIncrement; + } + pl__add_vertex(ptLayer, topRightEnd, tColor, (plVec2){gptCtx->ptFontAtlas->afWhiteUv[0], gptCtx->ptFontAtlas->afWhiteUv[1]}); + + for(uint32_t i = 0; i < numTriangles - 1; i++) + pl__add_index(ptLayer, uVertexStart, i + 1, 0, i + 2); + pl__add_index(ptLayer, uVertexStart, numTriangles, 0, 1); + +} + +static void +pl_add_quad(plDrawLayer2D* ptLayer, plVec2 tP0, plVec2 tP1, plVec2 tP2, plVec2 tP3, plVec4 tColor, float fThickness) +{ + pl_sb_push(ptLayer->sbtPath, tP0); + pl_sb_push(ptLayer->sbtPath, tP1); + pl_sb_push(ptLayer->sbtPath, tP2); + pl_sb_push(ptLayer->sbtPath, tP3); + pl_sb_push(ptLayer->sbtPath, tP0); + pl__submit_path(ptLayer, tColor, fThickness); +} + +static void +pl_add_quad_filled(plDrawLayer2D* ptLayer, plVec2 tP0, plVec2 tP1, plVec2 tP2, plVec2 tP3, plVec4 tColor) +{ + pl__prepare_draw_command(ptLayer, gptCtx->ptFontAtlas->tTexture, false); + pl__reserve_triangles(ptLayer, 6, 4); + + const uint32_t uVtxStart = pl_sb_size(ptLayer->ptDrawlist->sbtVertexBuffer); + pl__add_vertex(ptLayer, tP0, tColor, (plVec2){gptCtx->ptFontAtlas->afWhiteUv[0], gptCtx->ptFontAtlas->afWhiteUv[1]}); // top left + pl__add_vertex(ptLayer, tP1, tColor, (plVec2){gptCtx->ptFontAtlas->afWhiteUv[0], gptCtx->ptFontAtlas->afWhiteUv[1]}); // bot left + pl__add_vertex(ptLayer, tP2, tColor, (plVec2){gptCtx->ptFontAtlas->afWhiteUv[0], gptCtx->ptFontAtlas->afWhiteUv[1]}); // bot right + pl__add_vertex(ptLayer, tP3, tColor, (plVec2){gptCtx->ptFontAtlas->afWhiteUv[0], gptCtx->ptFontAtlas->afWhiteUv[1]}); // top right + + pl__add_index(ptLayer, uVtxStart, 0, 1, 2); + pl__add_index(ptLayer, uVtxStart, 0, 2, 3); +} + +static void +pl_add_circle(plDrawLayer2D* ptLayer, plVec2 tP, float fRadius, plVec4 tColor, uint32_t uSegments, float fThickness) +{ + if(uSegments == 0){ uSegments = 12; } + const float fIncrement = PL_2PI / uSegments; + float fTheta = 0.0f; + for(uint32_t i = 0; i < uSegments; i++) + { + pl_sb_push(ptLayer->sbtPath, ((plVec2){tP.x + fRadius * sinf(fTheta + PL_PI_2), tP.y + fRadius * sinf(fTheta)})); + fTheta += fIncrement; + } + pl_sb_push(ptLayer->sbtPath, ((plVec2){tP.x + fRadius, tP.y})); + pl__submit_path(ptLayer, tColor, fThickness); +} + +static void +pl_add_circle_filled(plDrawLayer2D* ptLayer, plVec2 tP, float fRadius, plVec4 tColor, uint32_t uSegments) +{ + if(uSegments == 0){ uSegments = 12; } + pl__prepare_draw_command(ptLayer, gptCtx->ptFontAtlas->tTexture, false); + pl__reserve_triangles(ptLayer, 3 * uSegments, uSegments + 1); + + const uint32_t uVertexStart = pl_sb_size(ptLayer->ptDrawlist->sbtVertexBuffer); + pl__add_vertex(ptLayer, tP, tColor, (plVec2){gptCtx->ptFontAtlas->afWhiteUv[0], gptCtx->ptFontAtlas->afWhiteUv[1]}); + + const float fIncrement = PL_2PI / uSegments; + float fTheta = 0.0f; + for(uint32_t i = 0; i < uSegments; i++) + { + pl__add_vertex(ptLayer, ((plVec2){tP.x + (fRadius * sinf(fTheta + PL_PI_2)), tP.y + (fRadius * sinf(fTheta))}), tColor, (plVec2){gptCtx->ptFontAtlas->afWhiteUv[0], gptCtx->ptFontAtlas->afWhiteUv[1]}); + fTheta += fIncrement; + } + + for(uint32_t i = 0; i < uSegments - 1; i++) + pl__add_index(ptLayer, uVertexStart, i + 1, 0, i + 2); + pl__add_index(ptLayer, uVertexStart, uSegments, 0, 1); +} + +static void +pl_add_image_ex(plDrawLayer2D* ptLayer, plTextureHandle tTexture, plVec2 tPMin, plVec2 tPMax, plVec2 tUvMin, plVec2 tUvMax, plVec4 tColor) +{ + pl__prepare_draw_command(ptLayer, tTexture, false); + pl__reserve_triangles(ptLayer, 6, 4); + + const plVec2 bottomLeft = { tPMin.x, tPMax.y }; + const plVec2 topRight = { tPMax.x, tPMin.y }; + + const uint32_t vertexStart = pl_sb_size(ptLayer->ptDrawlist->sbtVertexBuffer); + pl__add_vertex(ptLayer, tPMin, tColor, tUvMin); + pl__add_vertex(ptLayer, bottomLeft, tColor, (plVec2){tUvMin.x, tUvMax.y}); + pl__add_vertex(ptLayer, tPMax, tColor, tUvMax); + pl__add_vertex(ptLayer, topRight, tColor, (plVec2){tUvMax.x, tUvMin.y}); + + pl__add_index(ptLayer, vertexStart, 0, 1, 2); + pl__add_index(ptLayer, vertexStart, 0, 2, 3); +} + +static void +pl_add_image(plDrawLayer2D* ptLayer, plTextureHandle tTexture, plVec2 tPMin, plVec2 tPMax) +{ + pl_add_image_ex(ptLayer, tTexture, tPMin, tPMax, (plVec2){0}, (plVec2){1.0f, 1.0f}, (plVec4){1.0f, 1.0f, 1.0f, 1.0f}); +} + +static void +pl_add_bezier_quad(plDrawLayer2D* ptLayer, plVec2 tP0, plVec2 tP1, plVec2 tP2, plVec4 tColor, float fThickness, uint32_t uSegments) +{ + // order of the bezier curve inputs are 0=start, 1=control, 2=ending + + if(uSegments == 0) + uSegments = 12; + + // push first point + pl_sb_push(ptLayer->sbtPath, tP0); + + // calculate and push points between first and last + for (int i = 1; i < (int)uSegments; i++) + { + const float t = i / (float)uSegments; + const float u = 1.0f - t; + const float tt = t * t; + const float uu = u * u; + + const plVec2 p0 = pl_mul_vec2_scalarf(tP0, uu); + const plVec2 p1 = pl_mul_vec2_scalarf(tP1, (2.0f * u * t)); + const plVec2 p2 = pl_mul_vec2_scalarf(tP2, tt); + const plVec2 p3 = pl_add_vec2(p0,p1); + const plVec2 p4 = pl_add_vec2(p2,p3); + + pl_sb_push(ptLayer->sbtPath, p4); + } + + // push last point + pl_sb_push(ptLayer->sbtPath, tP2); + + pl__submit_path(ptLayer, tColor, fThickness); +} + +static void +pl_add_bezier_cubic(plDrawLayer2D* ptLayer, plVec2 tP0, plVec2 tP1, plVec2 tP2, plVec2 tP3, plVec4 tColor, float fThickness, uint32_t uSegments) +{ + + // order of the bezier curve inputs are 0=start, 1=control 1, 2=control 2, 3=ending + + if(uSegments == 0) + uSegments = 12; + + // push first point + pl_sb_push(ptLayer->sbtPath, tP0); + + // calculate and push points between first and last + for (int i = 1; i < (int)uSegments; i++) + { + const float t = i / (float)uSegments; + const float u = 1.0f - t; + const float tt = t * t; + const float uu = u * u; + const float uuu = uu * u; + const float ttt = tt * t; + + const plVec2 p0 = pl_mul_vec2_scalarf(tP0, uuu); + const plVec2 p1 = pl_mul_vec2_scalarf(tP1, (3.0f * uu * t)); + const plVec2 p2 = pl_mul_vec2_scalarf(tP2, (3.0f * u * tt)); + const plVec2 p3 = pl_mul_vec2_scalarf(tP3, (ttt)); + const plVec2 p5 = pl_add_vec2(p0,p1); + const plVec2 p6 = pl_add_vec2(p2,p3); + const plVec2 p7 = pl_add_vec2(p5,p6); + + pl_sb_push(ptLayer->sbtPath, p7); + } + + // push last point + pl_sb_push(ptLayer->sbtPath, tP3); + + pl__submit_path(ptLayer, tColor, fThickness); +} + +static char* +plu__read_file(const char* file) +{ + FILE* fileHandle = fopen(file, "rb"); + + if(fileHandle == NULL) + { + PL_ASSERT(false && "TTF file not found."); + return NULL; + } + + // obtain file size + fseek(fileHandle, 0, SEEK_END); + uint32_t fileSize = ftell(fileHandle); + fseek(fileHandle, 0, SEEK_SET); + + // allocate buffer + char* data = PL_ALLOC(fileSize); + + // copy file into buffer + size_t result = fread(data, sizeof(char), fileSize, fileHandle); + if(result != fileSize) + { + if(feof(fileHandle)) + { + PL_ASSERT(false && "Error reading TTF file: unexpected end of file"); + } + else if (ferror(fileHandle)) + { + PL_ASSERT(false && "Error reading TTF file."); + } + PL_ASSERT(false && "TTF file not read."); + } + + fclose(fileHandle); + return data; +} + +static void +pl_add_font_from_memory_ttf(plFontAtlas* atlas, plFontConfig config, void* data) +{ + atlas->bDirty = true; + atlas->iGlyphPadding = 1; + + plFont font = + { + .tConfig = config + }; + + // prepare stb + plFontPrepData prep = {0}; + stbtt_InitFont(&prep.fontInfo, (unsigned char*)data, 0); + + // get vertical font metrics + int ascent, descent, lineGap; + stbtt_GetFontVMetrics(&prep.fontInfo, &ascent, &descent, &lineGap); + + // calculate scaling factor + prep.scale = 1.0f; + if(font.tConfig.fFontSize > 0) prep.scale = stbtt_ScaleForPixelHeight(&prep.fontInfo, font.tConfig.fFontSize); + else prep.scale = stbtt_ScaleForMappingEmToPixels(&prep.fontInfo, -font.tConfig.fFontSize); + + // calculate SDF pixel increment + if(font.tConfig.bSdf) font.tConfig.fSdfPixelDistScale = (float)font.tConfig.ucOnEdgeValue / (float) font.tConfig.iSdfPadding; + + // calculate base line spacing + font.fAscent = ceilf(ascent * prep.scale); + font.fDescent = floorf(descent * prep.scale); + font.fLineSpacing = (font.fAscent - font.fDescent + prep.scale * (float)lineGap); + + // convert individual chars to ranges + for(uint32_t i = 0; i < pl_sb_size(font.tConfig.sbiIndividualChars); i++) + { + plFontRange range = { + .uCharCount = 1, + .iFirstCodePoint = font.tConfig.sbiIndividualChars[i], + .ptFontChar = NULL + }; + pl_sb_push(font.tConfig.sbtRanges, range); + } + + // find total number of glyphs/chars required + uint32_t totalCharCount = 0u; + for(uint32_t i = 0; i < pl_sb_size(font.tConfig.sbtRanges); i++) + totalCharCount += font.tConfig.sbtRanges[i].uCharCount; + + pl_sb_reserve(font.sbtGlyphs, totalCharCount); + pl_sb_resize(font.sbtCharData, totalCharCount); + + if(font.tConfig.bSdf) + { + pl_sb_reserve(atlas->sbtCustomRects, pl_sb_size(atlas->sbtCustomRects) + totalCharCount); // is this correct + } + + prep.ranges = PL_ALLOC(sizeof(stbtt_pack_range) * pl_sb_size(font.tConfig.sbtRanges)); + memset(prep.ranges, 0, sizeof(stbtt_pack_range) * pl_sb_size(font.tConfig.sbtRanges)); + + // find max codepoint & set range pointers into font char data + int k = 0; + int maxCodePoint = 0; + totalCharCount = 0u; + bool missingGlyphAdded = false; + + for(uint32_t i = 0; i < pl_sb_size(font.tConfig.sbtRanges); i++) + { + plFontRange* range = &font.tConfig.sbtRanges[i]; + prep.uTotalCharCount += range->uCharCount; + } + + if(!font.tConfig.bSdf) + { + prep.rects = PL_ALLOC(sizeof(stbrp_rect) * prep.uTotalCharCount); + } + + for(uint32_t i = 0; i < pl_sb_size(font.tConfig.sbtRanges); i++) + { + plFontRange* range = &font.tConfig.sbtRanges[i]; + + if(range->iFirstCodePoint + (int)range->uCharCount > maxCodePoint) + maxCodePoint = range->iFirstCodePoint + (int)range->uCharCount; + + range->ptFontChar = &font.sbtCharData[totalCharCount]; + + // prepare stb stuff + prep.ranges[i].font_size = font.tConfig.fFontSize; + prep.ranges[i].first_unicode_codepoint_in_range = range->iFirstCodePoint; + prep.ranges[i].chardata_for_range = (stbtt_packedchar*)range->ptFontChar; + prep.ranges[i].num_chars = range->uCharCount; + prep.ranges[i].h_oversample = (unsigned char) font.tConfig.uHOverSampling; + prep.ranges[i].v_oversample = (unsigned char) font.tConfig.uVOverSampling; + + // flag all characters as NOT packed + memset(prep.ranges[i].chardata_for_range, 0, sizeof(stbtt_packedchar) * range->uCharCount); + + if(font.tConfig.bSdf) + { + for (uint32_t j = 0; j < (uint32_t)prep.ranges[i].num_chars; j++) + { + int codePoint = 0; + if(prep.ranges[i].array_of_unicode_codepoints) codePoint = prep.ranges[i].array_of_unicode_codepoints[j]; + else codePoint = prep.ranges[i].first_unicode_codepoint_in_range + j; + + + int width = 0u; + int height = 0u; + int xOff = 0u; + int yOff = 0u; + unsigned char* bytes = stbtt_GetCodepointSDF(&prep.fontInfo, stbtt_ScaleForPixelHeight(&prep.fontInfo, font.tConfig.fFontSize), codePoint, font.tConfig.iSdfPadding, font.tConfig.ucOnEdgeValue, font.tConfig.fSdfPixelDistScale, &width, &height, &xOff, &yOff); + + int xAdvance = 0u; + stbtt_GetCodepointHMetrics(&prep.fontInfo, codePoint, &xAdvance, NULL); + + range->ptFontChar[j].xOff = (float)(xOff); + range->ptFontChar[j].yOff = (float)(yOff); + range->ptFontChar[j].xOff2 = (float)(xOff + width); + range->ptFontChar[j].yOff2 = (float)(yOff + height); + range->ptFontChar[j].xAdv = prep.scale * (float)xAdvance; + + plFontCustomRect customRect = { + .uWidth = (uint32_t)width, + .uHeight = (uint32_t)height, + .pucBytes = bytes + }; + pl_sb_push(atlas->sbtCustomRects, customRect); + prep.area += width * height; + + } + k += prep.ranges[i].num_chars; + } + else // regular font + { + for(uint32_t j = 0; j < range->uCharCount; j++) + { + int codepoint = 0; + if(prep.ranges[i].array_of_unicode_codepoints) codepoint = prep.ranges[i].array_of_unicode_codepoints[j]; + else codepoint = prep.ranges[i].first_unicode_codepoint_in_range + j; + + // bitmap + int glyphIndex = stbtt_FindGlyphIndex(&prep.fontInfo, codepoint); + if(glyphIndex == 0 && missingGlyphAdded) + prep.rects[k].w = prep.rects[k].h = 0; + else + { + int x0 = 0; + int y0 = 0; + int x1 = 0; + int y1 = 0; + stbtt_GetGlyphBitmapBoxSubpixel(&prep.fontInfo, glyphIndex, + prep.scale * font.tConfig.uHOverSampling, + prep.scale * font.tConfig.uVOverSampling, + 0, 0, &x0, &y0, &x1, &y1); + prep.rects[k].w = (stbrp_coord)(x1 - x0 + atlas->iGlyphPadding + font.tConfig.uHOverSampling - 1); + prep.rects[k].h = (stbrp_coord)(y1 - y0 + atlas->iGlyphPadding + font.tConfig.uVOverSampling - 1); + prep.area += prep.rects[k].w * prep.rects[k].h; + if (glyphIndex == 0) missingGlyphAdded = true; + } + k++; + } + } + totalCharCount += range->uCharCount; + } + pl_sb_resize(font.sbuCodePoints, (uint32_t)maxCodePoint); + + // add font to atlas + font.ptParentAtlas = atlas; + pl_sb_push(atlas->sbtFonts, font); + pl_sb_push(atlas->_sbtPrepData, prep); +} + +static void +pl_add_font_from_file_ttf(plFontAtlas* atlas, plFontConfig config, const char* file) +{ + void* data = plu__read_file(file); // freed after atlas is created + pl_add_font_from_memory_ttf(atlas, config, data); +} + +static plVec2 +pl_calculate_text_size_ex(plFont* font, float size, const char* text, const char* pcTextEnd, float wrap) +{ + plVec2 result = {0}; + plVec2 cursor = {0}; + + float scale = size > 0.0f ? size / font->tConfig.fFontSize : 1.0f; + + float fLineSpacing = scale * font->fLineSpacing; + plVec2 originalPosition = {FLT_MAX, FLT_MAX}; + bool firstCharacter = true; + + while(text < pcTextEnd) + { + uint32_t c = (uint32_t)*text; + if(c < 0x80) + text += 1; + else + { + text += pl_text_char_from_utf8(&c, text, NULL); + if(c == 0) // malformed UTF-8? + break; + } + + if(c == '\n') + { + cursor.x = originalPosition.x; + cursor.y += fLineSpacing; + } + else if(c == '\r') + { + // do nothing + } + else + { + + bool glyphFound = false; + for(uint32_t i = 0u; i < pl_sb_size(font->tConfig.sbtRanges); i++) + { + if (c >= (uint32_t)font->tConfig.sbtRanges[i].iFirstCodePoint && c < (uint32_t)font->tConfig.sbtRanges[i].iFirstCodePoint + (uint32_t)font->tConfig.sbtRanges[i].uCharCount) + { + + + float x0,y0,s0,t0; // top-left + float x1,y1,s1,t1; // bottom-right + + const plFontGlyph* glyph = &font->sbtGlyphs[font->sbuCodePoints[c]]; + + // adjust for left side bearing if first char + if(firstCharacter) + { + if(glyph->leftBearing > 0.0f) cursor.x += glyph->leftBearing * scale; + firstCharacter = false; + originalPosition.x = cursor.x + glyph->x0 * scale; + originalPosition.y = cursor.y + glyph->y0 * scale; + } + + x0 = cursor.x + glyph->x0 * scale; + x1 = cursor.x + glyph->x1 * scale; + y0 = cursor.y + glyph->y0 * scale; + y1 = cursor.y + glyph->y1 * scale; + + if(wrap > 0.0f && x1 > originalPosition.x + wrap) + { + x0 = originalPosition.x + glyph->x0 * scale; + y0 = y0 + fLineSpacing; + x1 = originalPosition.x + glyph->x1 * scale; + y1 = y1 + fLineSpacing; + + cursor.x = originalPosition.x; + cursor.y += fLineSpacing; + } + + if(x0 < originalPosition.x) originalPosition.x = x0; + if(y0 < originalPosition.y) originalPosition.y = y0; + + s0 = glyph->u0; + t0 = glyph->v0; + s1 = glyph->u1; + t1 = glyph->v1; + + if(x1 > result.x) result.x = x1; + if(y1 > result.y) result.y = y1; + + cursor.x += glyph->xAdvance * scale; + glyphFound = true; + break; + } + } + + PL_ASSERT(glyphFound && "Glyph not found"); + } + } + + return pl_sub_vec2(result, originalPosition); +} + +static plVec2 +pl_calculate_text_size(plFont* font, float size, const char* text, float wrap) +{ + const char* pcTextEnd = text + strlen(text); + return pl_calculate_text_size_ex(font, size, text, pcTextEnd, wrap); +} + +static plRect +pl_calculate_text_bb_ex(plFont* font, float size, plVec2 tP, const char* text, const char* pcTextEnd, float wrap) +{ + plVec2 tTextSize = {0}; + plVec2 cursor = {0}; + + float scale = size > 0.0f ? size / font->tConfig.fFontSize : 1.0f; + + float fLineSpacing = scale * font->fLineSpacing; + plVec2 originalPosition = {FLT_MAX, FLT_MAX}; + bool firstCharacter = true; + + while(text < pcTextEnd) + { + uint32_t c = (uint32_t)*text; + if(c < 0x80) + text += 1; + else + { + text += pl_text_char_from_utf8(&c, text, NULL); + if(c == 0) // malformed UTF-8? + break; + } + + if(c == '\n') + { + cursor.x = originalPosition.x; + cursor.y += fLineSpacing; + } + else if(c == '\r') + { + // do nothing + } + else + { + + bool glyphFound = false; + for(uint32_t i = 0u; i < pl_sb_size(font->tConfig.sbtRanges); i++) + { + if (c >= (uint32_t)font->tConfig.sbtRanges[i].iFirstCodePoint && c < (uint32_t)font->tConfig.sbtRanges[i].iFirstCodePoint + (uint32_t)font->tConfig.sbtRanges[i].uCharCount) + { + + float x0,y0,s0,t0; // top-left + float x1,y1,s1,t1; // bottom-right + + const plFontGlyph* glyph = &font->sbtGlyphs[font->sbuCodePoints[c]]; + + // adjust for left side bearing if first char + if(firstCharacter) + { + if(glyph->leftBearing > 0.0f) cursor.x += glyph->leftBearing * scale; + firstCharacter = false; + originalPosition.x = cursor.x + glyph->x0 * scale; + originalPosition.y = cursor.y + glyph->y0 * scale; + } + + x0 = cursor.x + glyph->x0 * scale; + x1 = cursor.x + glyph->x1 * scale; + y0 = cursor.y + glyph->y0 * scale; + y1 = cursor.y + glyph->y1 * scale; + + if(wrap > 0.0f && x1 > originalPosition.x + wrap) + { + x0 = originalPosition.x + glyph->x0 * scale; + y0 = y0 + fLineSpacing; + x1 = originalPosition.x + glyph->x1 * scale; + y1 = y1 + fLineSpacing; + + cursor.x = originalPosition.x; + cursor.y += fLineSpacing; + } + + if(x0 < originalPosition.x) originalPosition.x = x0; + if(y0 < originalPosition.y) originalPosition.y = y0; + + s0 = glyph->u0; + t0 = glyph->v0; + s1 = glyph->u1; + t1 = glyph->v1; + + if(x1 > tTextSize.x) + tTextSize.x = x1; + if(y1 > tTextSize.y) + tTextSize.y = y1; + + cursor.x += glyph->xAdvance * scale; + glyphFound = true; + break; + } + } + + PL_ASSERT(glyphFound && "Glyph not found"); + } + } + + tTextSize = pl_sub_vec2(tTextSize, originalPosition); + + const plVec2 tStartOffset = pl_add_vec2(tP, originalPosition); + + const plRect tResult = pl_calculate_rect(tStartOffset, tTextSize); + + return tResult; +} + +static plRect +pl_calculate_text_bb(plFont* ptFont, float fSize, plVec2 tP, const char* pcText, float fWrap) +{ + const char* pcTextEnd = pcText + strlen(pcText); + return pl_calculate_text_bb_ex(ptFont, fSize, tP, pcText, pcTextEnd, fWrap); +} + +static void +pl_push_clip_rect_pt(plDrawList2D* ptDrawlist, const plRect* ptRect) +{ + pl_sb_push(ptDrawlist->sbtClipStack, *ptRect); +} + +static void +pl_push_clip_rect(plDrawList2D* ptDrawlist, plRect tRect, bool bAccumulate) +{ + if(bAccumulate && pl_sb_size(ptDrawlist->sbtClipStack) > 0) + tRect = pl_rect_clip_full(&tRect, &pl_sb_back(ptDrawlist->sbtClipStack)); + pl_sb_push(ptDrawlist->sbtClipStack, tRect); +} + +static void +pl_pop_clip_rect(plDrawList2D* ptDrawlist) +{ + pl_sb_pop(ptDrawlist->sbtClipStack); +} + +static const plRect* +pl_get_clip_rect(plDrawList2D* ptDrawlist) +{ + if(pl_sb_size(ptDrawlist->sbtClipStack) > 0) + return &pl_sb_back(ptDrawlist->sbtClipStack); + return NULL; +} + +static void +pl_build_font_atlas(plFontAtlas* atlas) +{ + + gptCtx->ptFontAtlas = atlas; + + // calculate texture total area needed + uint32_t totalAtlasArea = 0u; + for(uint32_t i = 0u; i < pl_sb_size(atlas->_sbtPrepData); i++) + totalAtlasArea += atlas->_sbtPrepData[i].area; + + // create our white location + plFontCustomRect ptWhiteRect = { + .uWidth = 8u, + .uHeight = 8u, + .uX = 0u, + .uY = 0u, + .pucBytes = malloc(64) + }; + memset(ptWhiteRect.pucBytes, 255, 64); + pl_sb_push(atlas->sbtCustomRects, ptWhiteRect); + atlas->ptWhiteRect = &pl_sb_back(atlas->sbtCustomRects); + totalAtlasArea += 64; + + // calculate final texture area required + const float totalAtlasAreaSqrt = (float)sqrt((float)totalAtlasArea) + 1.0f; + atlas->auAtlasSize[0] = 512; + atlas->auAtlasSize[1] = 0; + if (totalAtlasAreaSqrt >= 4096 * 0.7f) atlas->auAtlasSize[0] = 4096; + else if(totalAtlasAreaSqrt >= 2048 * 0.7f) atlas->auAtlasSize[0] = 2048; + else if(totalAtlasAreaSqrt >= 1024 * 0.7f) atlas->auAtlasSize[0] = 1024; + + // begin packing + stbtt_pack_context spc = {0}; + stbtt_PackBegin(&spc, NULL, atlas->auAtlasSize[0], 1024 * 32, 0, atlas->iGlyphPadding, NULL); + + // allocate SDF rects + stbrp_rect* rects = PL_ALLOC(pl_sb_size(atlas->sbtCustomRects) * sizeof(stbrp_rect)); + memset(rects, 0, sizeof(stbrp_rect) * pl_sb_size(atlas->sbtCustomRects)); + + // transfer our data to stb data + for(uint32_t i = 0u; i < pl_sb_size(atlas->sbtCustomRects); i++) + { + rects[i].w = (int)atlas->sbtCustomRects[i].uWidth; + rects[i].h = (int)atlas->sbtCustomRects[i].uHeight; + } + + // pack bitmap fonts + for(uint32_t i = 0u; i < pl_sb_size(atlas->_sbtPrepData); i++) + { + plFont* font = &atlas->sbtFonts[i]; + if(!font->tConfig.bSdf) + { + plFontPrepData* prep = &atlas->_sbtPrepData[i]; + stbtt_PackSetOversampling(&spc, font->tConfig.uHOverSampling, font->tConfig.uVOverSampling); + stbrp_pack_rects((stbrp_context*)spc.pack_info, prep->rects, prep->uTotalCharCount); + for(uint32_t j = 0u; j < prep->uTotalCharCount; j++) + { + if(prep->rects[j].was_packed) + atlas->auAtlasSize[1] = (uint32_t)pl__get_max((float)atlas->auAtlasSize[1], (float)(prep->rects[j].y + prep->rects[j].h)); + } + } + } + + // pack SDF fonts + stbtt_PackSetOversampling(&spc, 1, 1); + stbrp_pack_rects((stbrp_context*)spc.pack_info, rects, pl_sb_size(atlas->sbtCustomRects)); + + for(uint32_t i = 0u; i < pl_sb_size(atlas->sbtCustomRects); i++) + { + if(rects[i].was_packed) + atlas->auAtlasSize[1] = (uint32_t)pl__get_max((float)atlas->auAtlasSize[1], (float)(rects[i].y + rects[i].h)); + } + + // grow cpu side buffers if needed + if(atlas->szPixelDataSize < atlas->auAtlasSize[0] * atlas->auAtlasSize[1]) + { + if(atlas->pucPixelsAsAlpha8) PL_FREE(atlas->pucPixelsAsAlpha8); + if(atlas->pucPixelsAsRGBA32) PL_FREE(atlas->pucPixelsAsRGBA32); + + atlas->pucPixelsAsAlpha8 = PL_ALLOC(atlas->auAtlasSize[0] * atlas->auAtlasSize[1]); + atlas->pucPixelsAsRGBA32 = PL_ALLOC(atlas->auAtlasSize[0] * atlas->auAtlasSize[1] * 4); + + memset(atlas->pucPixelsAsAlpha8, 0, atlas->auAtlasSize[0] * atlas->auAtlasSize[1]); + memset(atlas->pucPixelsAsRGBA32, 0, atlas->auAtlasSize[0] * atlas->auAtlasSize[1] * 4); + } + spc.pixels = atlas->pucPixelsAsAlpha8; + atlas->szPixelDataSize = atlas->auAtlasSize[0] * atlas->auAtlasSize[1]; + + // rasterize bitmap fonts + for(uint32_t i = 0u; i < pl_sb_size(atlas->_sbtPrepData); i++) + { + plFont* font = &atlas->sbtFonts[i]; + plFontPrepData* prep = &atlas->_sbtPrepData[i]; + if(!atlas->sbtFonts[i].tConfig.bSdf) + stbtt_PackFontRangesRenderIntoRects(&spc, &prep->fontInfo, prep->ranges, pl_sb_size(font->tConfig.sbtRanges), prep->rects); + } + + // update SDF/custom data + for(uint32_t i = 0u; i < pl_sb_size(atlas->sbtCustomRects); i++) + { + atlas->sbtCustomRects[i].uX = (uint32_t)rects[i].x; + atlas->sbtCustomRects[i].uY = (uint32_t)rects[i].y; + } + + uint32_t charDataOffset = 0u; + for(uint32_t fontIndex = 0u; fontIndex < pl_sb_size(atlas->sbtFonts); fontIndex++) + { + plFont* font = &atlas->sbtFonts[fontIndex]; + if(font->tConfig.bSdf) + { + for(uint32_t i = 0u; i < pl_sb_size(font->sbtCharData); i++) + { + font->sbtCharData[i].x0 = (uint16_t)rects[charDataOffset + i].x; + font->sbtCharData[i].y0 = (uint16_t)rects[charDataOffset + i].y; + font->sbtCharData[i].x1 = (uint16_t)(rects[charDataOffset + i].x + atlas->sbtCustomRects[charDataOffset + i].uWidth); + font->sbtCharData[i].y1 = (uint16_t)(rects[charDataOffset + i].y + atlas->sbtCustomRects[charDataOffset + i].uHeight); + + } + charDataOffset += pl_sb_size(font->sbtCharData); + } + } + + // end packing + stbtt_PackEnd(&spc); + + // rasterize SDF/custom rects + for(uint32_t r = 0u; r < pl_sb_size(atlas->sbtCustomRects); r++) + { + plFontCustomRect* customRect = &atlas->sbtCustomRects[r]; + for(uint32_t i = 0u; i < customRect->uHeight; i++) + { + for(uint32_t j = 0u; j < customRect->uWidth; j++) + atlas->pucPixelsAsAlpha8[(customRect->uY + i) * atlas->auAtlasSize[0] + (customRect->uX + j)] = customRect->pucBytes[i * customRect->uWidth + j]; + } + stbtt_FreeSDF(customRect->pucBytes, NULL); + customRect->pucBytes = NULL; + } + + // update white point uvs + atlas->afWhiteUv[0] = (float)(atlas->ptWhiteRect->uX + atlas->ptWhiteRect->uWidth / 2) / (float)atlas->auAtlasSize[0]; + atlas->afWhiteUv[1] = (float)(atlas->ptWhiteRect->uY + atlas->ptWhiteRect->uHeight / 2) / (float)atlas->auAtlasSize[1]; + + // add glyphs + for(uint32_t fontIndex = 0u; fontIndex < pl_sb_size(atlas->sbtFonts); fontIndex++) + { + plFont* font = &atlas->sbtFonts[fontIndex]; + + uint32_t charIndex = 0u; + float pixelHeight = 0.0f; + if(font->tConfig.bSdf) pixelHeight = 0.5f * 1.0f / (float)atlas->auAtlasSize[1]; // is this correct? + + for(uint32_t i = 0u; i < pl_sb_size(font->tConfig.sbtRanges); i++) + { + plFontRange* range = &font->tConfig.sbtRanges[i]; + for(uint32_t j = 0u; j < range->uCharCount; j++) + { + const int codePoint = range->iFirstCodePoint + j; + stbtt_aligned_quad q; + float unused_x = 0.0f, unused_y = 0.0f; + stbtt_GetPackedQuad((stbtt_packedchar*)font->sbtCharData, atlas->auAtlasSize[0], atlas->auAtlasSize[1], charIndex, &unused_x, &unused_y, &q, 0); + + int unusedAdvanced, leftSideBearing; + stbtt_GetCodepointHMetrics(&atlas->_sbtPrepData[fontIndex].fontInfo, codePoint, &unusedAdvanced, &leftSideBearing); + + plFontGlyph glyph = { + .x0 = q.x0, + .y0 = q.y0 + font->fAscent, + .x1 = q.x1, + .y1 = q.y1 + font->fAscent, + .u0 = q.s0, + .v0 = q.t0 + pixelHeight, + .u1 = q.s1, + .v1 = q.t1 - pixelHeight, + .xAdvance = font->sbtCharData[charIndex].xAdv, + .leftBearing = (float)leftSideBearing * atlas->_sbtPrepData[fontIndex].scale + }; + pl_sb_push(font->sbtGlyphs, glyph); + font->sbuCodePoints[codePoint] = pl_sb_size(font->sbtGlyphs) - 1; + charIndex++; + } + } + + PL_FREE(atlas->_sbtPrepData[fontIndex].fontInfo.data); + } + + // convert to 4 color channels + for(uint32_t i = 0u; i < atlas->auAtlasSize[0] * atlas->auAtlasSize[1]; i++) + { + atlas->pucPixelsAsRGBA32[i * 4] = 255; + atlas->pucPixelsAsRGBA32[i * 4 + 1] = 255; + atlas->pucPixelsAsRGBA32[i * 4 + 2] = 255; + atlas->pucPixelsAsRGBA32[i * 4 + 3] = atlas->pucPixelsAsAlpha8[i]; + } + + PL_FREE(rects); + + // create dummy texture + const plTextureDesc tFontTextureDesc = { + .tDimensions = {(float)atlas->auAtlasSize[0], (float)atlas->auAtlasSize[1], 1}, + .tFormat = PL_FORMAT_R8G8B8A8_UNORM, + .uLayers = 1, + .uMips = 1, + .tType = PL_TEXTURE_TYPE_2D, + .tUsage = PL_TEXTURE_USAGE_SAMPLED, + .tInitialUsage = PL_TEXTURE_USAGE_SAMPLED + }; + + plDevice* ptDevice = &gptCtx->ptGraphics->tDevice; + atlas->tTexture = gptDevice->create_texture(ptDevice, &tFontTextureDesc, "font texture"); + + plTexture* ptTexture = gptDevice->get_texture(ptDevice, atlas->tTexture); + + const plDeviceMemoryAllocation tAllocation = gptDevice->allocate_memory(ptDevice, + ptTexture->tMemoryRequirements.ulSize, + PL_MEMORY_GPU, + ptTexture->tMemoryRequirements.uMemoryTypeBits, + "font texture memory"); + + gptDevice->bind_texture_to_memory(ptDevice, atlas->tTexture, &tAllocation); + + const plBufferDescription tBufferDesc = { + .tUsage = PL_BUFFER_USAGE_STAGING, + .uByteSize = atlas->auAtlasSize[0] * atlas->auAtlasSize[1] * 4 + }; + plBufferHandle tStagingBuffer = pl__create_staging_buffer(&tBufferDesc, "font staging buffer", 0); + plBuffer* ptStagingBuffer = gptDevice->get_buffer(ptDevice, tStagingBuffer); + memcpy(ptStagingBuffer->tMemoryAllocation.pHostMapped, atlas->pucPixelsAsRGBA32, tBufferDesc.uByteSize); + + // begin recording + plCommandBuffer tCommandBuffer = gptGfx->begin_command_recording(gptCtx->ptGraphics, NULL); + + // begin blit pass, copy texture, end pass + plBlitEncoder tEncoder = gptGfx->begin_blit_pass(gptCtx->ptGraphics, &tCommandBuffer); + + const plBufferImageCopy tBufferImageCopy = { + .tImageExtent = {(uint32_t)atlas->auAtlasSize[0], (uint32_t)atlas->auAtlasSize[1], 1}, + .uLayerCount = 1 + }; + + gptGfx->copy_buffer_to_texture(&tEncoder, tStagingBuffer, atlas->tTexture, 1, &tBufferImageCopy); + gptGfx->end_blit_pass(&tEncoder); + + // finish recording + gptGfx->end_command_recording(gptCtx->ptGraphics, &tCommandBuffer); + + // submit command buffer + gptGfx->submit_command_buffer_blocking(gptCtx->ptGraphics, &tCommandBuffer, NULL); + + gptDevice->destroy_buffer(ptDevice, tStagingBuffer); +} + +static void +pl_cleanup_font_atlas(plFontAtlas* atlas) +{ + for(uint32_t i = 0; i < pl_sb_size(atlas->sbtFonts); i++) + { + plFont* font = &atlas->sbtFonts[i]; + pl_sb_free(font->tConfig.sbtRanges); + pl_sb_free(font->tConfig.sbiIndividualChars); + pl_sb_free(font->sbuCodePoints); + pl_sb_free(font->sbtGlyphs); + pl_sb_free(font->sbtCharData); + } + for(uint32_t i = 0; i < pl_sb_size(atlas->_sbtPrepData); i++) + { + PL_FREE(atlas->_sbtPrepData[i].ranges); + PL_FREE(atlas->_sbtPrepData[i].rects); + } + for(uint32_t i = 0; i < pl_sb_size(atlas->sbtCustomRects); i++) + { + PL_FREE(atlas->sbtCustomRects[i].pucBytes); + } + pl_sb_free(atlas->sbtCustomRects); + pl_sb_free(atlas->sbtFonts); + pl_sb_free(atlas->_sbtPrepData); + PL_FREE(atlas->pucPixelsAsAlpha8); + PL_FREE(atlas->pucPixelsAsRGBA32); + gptDevice->destroy_texture(&gptCtx->ptGraphics->tDevice, atlas->tTexture); +} + +static void +pl__prepare_draw_command(plDrawLayer2D* ptLayer, plTextureHandle textureID, bool sdf) +{ + bool createNewCommand = true; + + const plRect tCurrentClip = pl_sb_size(ptLayer->ptDrawlist->sbtClipStack) > 0 ? pl_sb_top(ptLayer->ptDrawlist->sbtClipStack) : (plRect){0}; + + + if(ptLayer->_ptLastCommand) + { + // check if last command has same texture + if(ptLayer->_ptLastCommand->tTextureId.ulData == textureID.ulData && ptLayer->_ptLastCommand->bSdf == sdf) + { + createNewCommand = false; + } + + // check if last command has same clipping + if(ptLayer->_ptLastCommand->tClip.tMax.x != tCurrentClip.tMax.x || ptLayer->_ptLastCommand->tClip.tMax.y != tCurrentClip.tMax.y || + ptLayer->_ptLastCommand->tClip.tMin.x != tCurrentClip.tMin.x || ptLayer->_ptLastCommand->tClip.tMin.y != tCurrentClip.tMin.y) + { + createNewCommand = true; + } + } + + // new command needed + if(createNewCommand) + { + plDrawCommand newdrawCommand = + { + .uVertexOffset = pl_sb_size(ptLayer->ptDrawlist->sbtVertexBuffer), + .uIndexOffset = pl_sb_size(ptLayer->sbuIndexBuffer), + .uElementCount = 0u, + .tTextureId = textureID, + .bSdf = sdf, + .tClip = tCurrentClip + }; + pl_sb_push(ptLayer->sbtCommandBuffer, newdrawCommand); + + } + ptLayer->_ptLastCommand = &pl_sb_top(ptLayer->sbtCommandBuffer); + ptLayer->_ptLastCommand->tTextureId = textureID; +} + +static void +pl__reserve_triangles(plDrawLayer2D* ptLayer, uint32_t indexCount, uint32_t uVertexCount) +{ + pl_sb_reserve(ptLayer->ptDrawlist->sbtVertexBuffer, pl_sb_size(ptLayer->ptDrawlist->sbtVertexBuffer) + uVertexCount); + pl_sb_reserve(ptLayer->sbuIndexBuffer, pl_sb_size(ptLayer->sbuIndexBuffer) + indexCount); + ptLayer->_ptLastCommand->uElementCount += indexCount; + ptLayer->uVertexCount += uVertexCount; +} + +static void +pl__add_vertex(plDrawLayer2D* ptLayer, plVec2 pos, plVec4 color, plVec2 uv) +{ + + uint32_t tcolor = 0; + tcolor = (uint32_t) (255.0f * color.r + 0.5f); + tcolor |= (uint32_t) (255.0f * color.g + 0.5f) << 8; + tcolor |= (uint32_t) (255.0f * color.b + 0.5f) << 16; + tcolor |= (uint32_t) (255.0f * color.a + 0.5f) << 24; + + pl_sb_push(ptLayer->ptDrawlist->sbtVertexBuffer, + ((plDrawVertex){ + .afPos[0] = pos.x, + .afPos[1] = pos.y, + .afUv[0] = uv.u, + .afUv[1] = uv.v, + .uColor = tcolor + }) + ); +} + +static void +pl__add_index(plDrawLayer2D* ptLayer, uint32_t vertexStart, uint32_t i0, uint32_t i1, uint32_t i2) +{ + pl_sb_push(ptLayer->sbuIndexBuffer, vertexStart + i0); + pl_sb_push(ptLayer->sbuIndexBuffer, vertexStart + i1); + pl_sb_push(ptLayer->sbuIndexBuffer, vertexStart + i2); +} + +static void +pl_new_draw_3d_frame(void) +{ + // reset 3d drawlists + for(uint32_t i = 0u; i < gptCtx->uDrawlistCount3D; i++) + { + plDrawList3D* ptDrawlist = gptCtx->aptDrawlists3D[i]; + + pl_sb_reset(ptDrawlist->sbtSolidVertexBuffer); + pl_sb_reset(ptDrawlist->sbtLineVertexBuffer); + pl_sb_reset(ptDrawlist->sbtSolidIndexBuffer); + pl_sb_reset(ptDrawlist->sbtLineIndexBuffer); + } + + // reset 3d drawlists + for(uint32_t i = 0u; i < gptCtx->uDrawlistCount2D; i++) + { + plDrawList2D* ptDrawlist = gptCtx->aptDrawlists2D[i]; + + ptDrawlist->uIndexBufferByteSize = 0; + + pl_sb_reset(ptDrawlist->sbtDrawCommands); + pl_sb_reset(ptDrawlist->sbtVertexBuffer); + for(uint32_t j = 0; j < pl_sb_size(ptDrawlist->sbtSubmittedLayers); j++) + { + pl_sb_reset(ptDrawlist->sbtSubmittedLayers[j]->sbtCommandBuffer); + pl_sb_reset(ptDrawlist->sbtSubmittedLayers[j]->sbuIndexBuffer); + pl_sb_reset(ptDrawlist->sbtSubmittedLayers[j]->sbtPath); + ptDrawlist->sbtSubmittedLayers[j]->uVertexCount = 0u; + ptDrawlist->sbtSubmittedLayers[j]->_ptLastCommand = NULL; + } + pl_sb_reset(ptDrawlist->sbtSubmittedLayers); + } + + // reset buffer offsets + for(uint32_t i = 0; i < PL_FRAMES_IN_FLIGHT; i++) + { + gptCtx->atBufferInfo[i].uVertexBufferOffset = 0; + gptCtx->at3DBufferInfo[i].uVertexBufferOffset = 0; + gptCtx->atLineBufferInfo[i].uVertexBufferOffset = 0; + gptCtx->auIndexBufferOffset[i] = 0; + } +} + +static void +pl__submit_2d_drawlist(plDrawList2D* ptDrawlist, plRenderEncoder tEncoder, float fWidth, float fHeight,uint32_t uMSAASampleCount) +{ + if(pl_sb_size(ptDrawlist->sbtVertexBuffer) == 0u) + return; + + plGraphics* ptGfx = gptCtx->ptGraphics; + + //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~vertex buffer prep~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + // ensure gpu vertex buffer size is adequate + const uint32_t uVtxBufSzNeeded = sizeof(plDrawVertex) * pl_sb_size(ptDrawlist->sbtVertexBuffer); + + plBufferInfo* ptBufferInfo = &gptCtx->atBufferInfo[ptGfx->uCurrentFrameIndex]; + + // space left in vertex buffer + const uint32_t uAvailableVertexBufferSpace = ptBufferInfo->uVertexBufferSize - ptBufferInfo->uVertexBufferOffset; + + // grow buffer if not enough room + if(uVtxBufSzNeeded >= uAvailableVertexBufferSpace) + { + + gptDevice->queue_buffer_for_deletion(&ptGfx->tDevice, ptBufferInfo->tVertexBuffer); + + const plBufferDescription tBufferDesc = { + .tUsage = PL_BUFFER_USAGE_VERTEX | PL_BUFFER_USAGE_STAGING, + .uByteSize = pl_max(ptBufferInfo->uVertexBufferSize * 2, uVtxBufSzNeeded + uAvailableVertexBufferSpace) + }; + ptBufferInfo->uVertexBufferSize = tBufferDesc.uByteSize; + + ptBufferInfo->tVertexBuffer = pl__create_staging_buffer(&tBufferDesc, "draw vtx buffer", ptGfx->uCurrentFrameIndex); + } + + // vertex GPU data transfer + plBuffer* ptVertexBuffer = gptDevice->get_buffer(&ptGfx->tDevice, ptBufferInfo->tVertexBuffer); + char* pucMappedVertexBufferLocation = ptVertexBuffer->tMemoryAllocation.pHostMapped; + memcpy(&pucMappedVertexBufferLocation[ptBufferInfo->uVertexBufferOffset], ptDrawlist->sbtVertexBuffer, sizeof(plDrawVertex) * pl_sb_size(ptDrawlist->sbtVertexBuffer)); + + //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~index buffer prep~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + // ensure gpu index buffer size is adequate + const uint32_t uIdxBufSzNeeded = sizeof(uint32_t) * ptDrawlist->uIndexBufferByteSize; + + // space left in index buffer + const uint32_t uAvailableIndexBufferSpace = gptCtx->auIndexBufferSize[ptGfx->uCurrentFrameIndex] - gptCtx->auIndexBufferOffset[ptGfx->uCurrentFrameIndex]; + + if(uIdxBufSzNeeded >= uAvailableIndexBufferSpace) + { + gptDevice->queue_buffer_for_deletion(&ptGfx->tDevice, gptCtx->atIndexBuffer[ptGfx->uCurrentFrameIndex]); + + const plBufferDescription tBufferDesc = { + .tUsage = PL_BUFFER_USAGE_INDEX | PL_BUFFER_USAGE_STAGING, + .uByteSize = pl_max(gptCtx->auIndexBufferSize[ptGfx->uCurrentFrameIndex] * 2, uIdxBufSzNeeded + uAvailableIndexBufferSpace) + }; + gptCtx->auIndexBufferSize[ptGfx->uCurrentFrameIndex] = tBufferDesc.uByteSize; + + gptCtx->atIndexBuffer[ptGfx->uCurrentFrameIndex] = pl__create_staging_buffer(&tBufferDesc, "draw idx buffer", ptGfx->uCurrentFrameIndex); + } + + plBuffer* ptIndexBuffer = gptDevice->get_buffer(&ptGfx->tDevice, gptCtx->atIndexBuffer[ptGfx->uCurrentFrameIndex]); + char* pucMappedIndexBufferLocation = ptIndexBuffer->tMemoryAllocation.pHostMapped; + + char* pucDestination = &pucMappedIndexBufferLocation[gptCtx->auIndexBufferOffset[ptGfx->uCurrentFrameIndex]]; + + // index GPU data transfer + uint32_t uTempIndexBufferOffset = 0u; + uint32_t globalIdxBufferIndexOffset = 0u; + + for(uint32_t i = 0u; i < pl_sb_size(ptDrawlist->sbtSubmittedLayers); i++) + { + plDrawCommand* ptLastCommand = NULL; + plDrawLayer2D* ptLayer = ptDrawlist->sbtSubmittedLayers[i]; + + memcpy(&pucDestination[uTempIndexBufferOffset], ptLayer->sbuIndexBuffer, sizeof(uint32_t) * pl_sb_size(ptLayer->sbuIndexBuffer)); + + uTempIndexBufferOffset += pl_sb_size(ptLayer->sbuIndexBuffer)*sizeof(uint32_t); + + // attempt to merge commands + for(uint32_t j = 0u; j < pl_sb_size(ptLayer->sbtCommandBuffer); j++) + { + plDrawCommand* ptLayerCommand = &ptLayer->sbtCommandBuffer[j]; + bool bCreateNewCommand = true; + + if(ptLastCommand) + { + // check for same texture (allows merging draw calls) + if(ptLastCommand->tTextureId.uIndex == ptLayerCommand->tTextureId.uIndex && ptLastCommand->bSdf == ptLayerCommand->bSdf) + { + ptLastCommand->uElementCount += ptLayerCommand->uElementCount; + bCreateNewCommand = false; + } + + // check for same clipping (allows merging draw calls) + if(ptLayerCommand->tClip.tMax.x != ptLastCommand->tClip.tMax.x || ptLayerCommand->tClip.tMax.y != ptLastCommand->tClip.tMax.y || + ptLayerCommand->tClip.tMin.x != ptLastCommand->tClip.tMin.x || ptLayerCommand->tClip.tMin.y != ptLastCommand->tClip.tMin.y) + { + bCreateNewCommand = true; + } + + } + + if(bCreateNewCommand) + { + ptLayerCommand->uIndexOffset = globalIdxBufferIndexOffset + ptLayerCommand->uIndexOffset; + pl_sb_push(ptDrawlist->sbtDrawCommands, *ptLayerCommand); + ptLastCommand = ptLayerCommand; + } + + } + globalIdxBufferIndexOffset += pl_sb_size(ptLayer->sbuIndexBuffer); + } + + const int32_t iVertexOffset = ptBufferInfo->uVertexBufferOffset / sizeof(plDrawVertex); + const int32_t iIndexOffset = gptCtx->auIndexBufferOffset[ptGfx->uCurrentFrameIndex] / sizeof(uint32_t); + + const plPipelineEntry* ptEntry = pl__get_2d_pipeline(tEncoder.tRenderPassHandle, uMSAASampleCount, tEncoder._uCurrentSubpass); + + const plVec2 tClipScale = {1.0f, 1.0f}; + // const plVec2 tClipScale = ptCtx->tFrameBufferScale; + const float fScale[] = { 2.0f / fWidth, 2.0f / fHeight}; + const float fTranslate[] = {-1.0f, -1.0f}; + plShaderHandle tCurrentShader = ptEntry->tRegularPipeline; + + typedef struct _plDrawDynamicData + { + plVec2 uScale; + plVec2 uTranslate; + } plDrawDynamicData; + + plDynamicBinding tDynamicBinding = gptDevice->allocate_dynamic_data(&gptCtx->ptGraphics->tDevice, sizeof(plDrawDynamicData)); + + plDrawDynamicData* ptDynamicData = (plDrawDynamicData*)tDynamicBinding.pcData; + ptDynamicData->uScale.x = 2.0f / fWidth; + ptDynamicData->uScale.y = 2.0f / fHeight; + ptDynamicData->uTranslate.x = -1.0f; + ptDynamicData->uTranslate.y = -1.0f; + + bool bSdf = false; + + plRenderViewport tViewport = { + .fWidth = fWidth, + .fHeight = fHeight, + .fMaxDepth = 1.0f + }; + + gptGfx->set_viewport(&tEncoder, &tViewport); + gptGfx->bind_vertex_buffer(&tEncoder, ptBufferInfo->tVertexBuffer); + gptGfx->bind_shader(&tEncoder, tCurrentShader); + + for(uint32_t i = 0u; i < pl_sb_size(ptDrawlist->sbtDrawCommands); i++) + { + plDrawCommand cmd = ptDrawlist->sbtDrawCommands[i]; + + if(cmd.bSdf && !bSdf) + { + gptGfx->bind_shader(&tEncoder, ptEntry->tSecondaryPipeline); + tCurrentShader = ptEntry->tSecondaryPipeline; + bSdf = true; + } + else if(!cmd.bSdf && bSdf) + { + gptGfx->bind_shader(&tEncoder, ptEntry->tRegularPipeline); + tCurrentShader = ptEntry->tRegularPipeline; + bSdf = false; + } + + if(pl_rect_width(&cmd.tClip) == 0) + { + const plScissor tScissor = { + .uWidth = (uint32_t)(fWidth * tClipScale.x), + .uHeight = (uint32_t)(fHeight * tClipScale.y), + }; + gptGfx->set_scissor_region(&tEncoder, &tScissor); + } + else + { + + // cmd.tClip.tMin.x = tFrameBufferScale.x * cmd.tClip.tMin.x; + // cmd.tClip.tMax.x = tFrameBufferScale.x * cmd.tClip.tMax.x; + // cmd.tClip.tMin.y = tFrameBufferScale.y * cmd.tClip.tMin.y; + // cmd.tClip.tMax.y = tFrameBufferScale.y * cmd.tClip.tMax.y; + + // clamp to viewport + if (cmd.tClip.tMin.x < 0.0f) { cmd.tClip.tMin.x = 0.0f; } + if (cmd.tClip.tMin.y < 0.0f) { cmd.tClip.tMin.y = 0.0f; } + if (cmd.tClip.tMax.x > fWidth) { cmd.tClip.tMax.x = (float)fWidth; } + if (cmd.tClip.tMax.y > fHeight) { cmd.tClip.tMax.y = (float)fHeight; } + if (cmd.tClip.tMax.x <= cmd.tClip.tMin.x || cmd.tClip.tMax.y <= cmd.tClip.tMin.y) + continue; + + const plScissor tScissor = { + .iOffsetX = (uint32_t) (cmd.tClip.tMin.x < 0 ? 0 : cmd.tClip.tMin.x), + .iOffsetY = (uint32_t) (cmd.tClip.tMin.y < 0 ? 0 : cmd.tClip.tMin.y), + .uWidth = (uint32_t)pl_rect_width(&cmd.tClip), + .uHeight = (uint32_t)pl_rect_height(&cmd.tClip) + }; + gptGfx->set_scissor_region(&tEncoder, &tScissor); + } + + plBindGroupHandle atBindGroups[] = { + gptCtx->tFontSamplerBindGroup, + gptDevice->get_texture(&gptCtx->ptGraphics->tDevice, cmd.tTextureId)->_tDrawBindGroup + }; + + gptGfx->bind_graphics_bind_groups(&tEncoder, tCurrentShader, 0, 2, atBindGroups, &tDynamicBinding); + + plDrawIndex tDraw = { + .tIndexBuffer = gptCtx->atIndexBuffer[gptCtx->ptGraphics->uCurrentFrameIndex], + .uIndexCount = cmd.uElementCount, + .uIndexStart = cmd.uIndexOffset + iIndexOffset, + .uInstance = 0, + .uInstanceCount = 1, + .uVertexStart = iVertexOffset + }; + gptGfx->draw_indexed(&tEncoder, 1, &tDraw); + } + + // bump vertex & index buffer offset + ptBufferInfo->uVertexBufferOffset += uVtxBufSzNeeded; + gptCtx->auIndexBufferOffset[ptGfx->uCurrentFrameIndex] += uIdxBufSzNeeded; +} + +static void +pl__submit_3d_drawlist(plDrawList3D* ptDrawlist, plRenderEncoder tEncoder, float fWidth, float fHeight, const plMat4* ptMVP, plDrawFlags tFlags, uint32_t uMSAASampleCount) +{ + plGraphics* ptGfx = gptCtx->ptGraphics; + + const plPipelineEntry* ptEntry = pl__get_3d_pipeline(tEncoder.tRenderPassHandle, uMSAASampleCount, tFlags, tEncoder._uCurrentSubpass); + + const float fAspectRatio = fWidth / fHeight; + + // regular 3D + if(pl_sb_size(ptDrawlist->sbtSolidVertexBuffer) > 0u) + { + //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~vertex buffer prep~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + // ensure gpu vertex buffer size is adequate + const uint32_t uVtxBufSzNeeded = sizeof(plDrawVertex3DSolid) * pl_sb_size(ptDrawlist->sbtSolidVertexBuffer); + + plBufferInfo* ptBufferInfo = &gptCtx->at3DBufferInfo[ptGfx->uCurrentFrameIndex]; + + // space left in vertex buffer + const uint32_t uAvailableVertexBufferSpace = ptBufferInfo->uVertexBufferSize - ptBufferInfo->uVertexBufferOffset; + + // grow buffer if not enough room + if(uVtxBufSzNeeded >= uAvailableVertexBufferSpace) + { + + gptDevice->queue_buffer_for_deletion(&ptGfx->tDevice, ptBufferInfo->tVertexBuffer); + + const plBufferDescription tBufferDesc = { + .tUsage = PL_BUFFER_USAGE_VERTEX | PL_BUFFER_USAGE_STAGING, + .uByteSize = pl_max(ptBufferInfo->uVertexBufferSize * 2, uVtxBufSzNeeded + uAvailableVertexBufferSpace) + }; + ptBufferInfo->uVertexBufferSize = tBufferDesc.uByteSize; + + ptBufferInfo->tVertexBuffer = pl__create_staging_buffer(&tBufferDesc, "3d draw vtx buffer", ptGfx->uCurrentFrameIndex); + } + + // vertex GPU data transfer + plBuffer* ptVertexBuffer = gptDevice->get_buffer(&ptGfx->tDevice, ptBufferInfo->tVertexBuffer); + char* pucMappedVertexBufferLocation = ptVertexBuffer->tMemoryAllocation.pHostMapped; + memcpy(&pucMappedVertexBufferLocation[ptBufferInfo->uVertexBufferOffset], ptDrawlist->sbtSolidVertexBuffer, sizeof(plDrawVertex3DSolid) * pl_sb_size(ptDrawlist->sbtSolidVertexBuffer)); + + //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~index buffer prep~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + // ensure gpu index buffer size is adequate + const uint32_t uIdxBufSzNeeded = sizeof(uint32_t) * pl_sb_size(ptDrawlist->sbtSolidIndexBuffer); + + // space left in index buffer + const uint32_t uAvailableIndexBufferSpace = gptCtx->auIndexBufferSize[ptGfx->uCurrentFrameIndex] - gptCtx->auIndexBufferOffset[ptGfx->uCurrentFrameIndex]; + + if(uIdxBufSzNeeded >= uAvailableIndexBufferSpace) + { + gptDevice->queue_buffer_for_deletion(&ptGfx->tDevice, gptCtx->atIndexBuffer[ptGfx->uCurrentFrameIndex]); + + const plBufferDescription tBufferDesc = { + .tUsage = PL_BUFFER_USAGE_INDEX | PL_BUFFER_USAGE_STAGING, + .uByteSize = pl_max(gptCtx->auIndexBufferSize[ptGfx->uCurrentFrameIndex] * 2, uIdxBufSzNeeded + uAvailableIndexBufferSpace) + }; + gptCtx->auIndexBufferSize[ptGfx->uCurrentFrameIndex] = tBufferDesc.uByteSize; + + gptCtx->atIndexBuffer[ptGfx->uCurrentFrameIndex] = pl__create_staging_buffer(&tBufferDesc, "3d draw idx buffer", ptGfx->uCurrentFrameIndex); + } + + // index GPU data transfer + plBuffer* ptIndexBuffer = gptDevice->get_buffer(&ptGfx->tDevice, gptCtx->atIndexBuffer[ptGfx->uCurrentFrameIndex]); + char* pucMappedIndexBufferLocation = ptIndexBuffer->tMemoryAllocation.pHostMapped; + memcpy(&pucMappedIndexBufferLocation[gptCtx->auIndexBufferOffset[ptGfx->uCurrentFrameIndex]], ptDrawlist->sbtSolidIndexBuffer, sizeof(uint32_t) * pl_sb_size(ptDrawlist->sbtSolidIndexBuffer)); + + plDynamicBinding tSolidDynamicData = gptDevice->allocate_dynamic_data(&ptGfx->tDevice, sizeof(plMat4)); + plMat4* ptSolidDynamicData = (plMat4*)tSolidDynamicData.pcData; + *ptSolidDynamicData = *ptMVP; + + gptGfx->bind_vertex_buffer(&tEncoder, ptBufferInfo->tVertexBuffer); + gptGfx->bind_shader(&tEncoder, ptEntry->tRegularPipeline); + gptGfx->bind_graphics_bind_groups(&tEncoder, ptEntry->tRegularPipeline, 0, 0, NULL, &tSolidDynamicData); + + const int32_t iVertexOffset = ptBufferInfo->uVertexBufferOffset / sizeof(plDrawVertex3DSolid); + const int32_t iIndexOffset = gptCtx->auIndexBufferOffset[ptGfx->uCurrentFrameIndex] / sizeof(uint32_t); + + const plDrawIndex tDrawIndex = { + .tIndexBuffer = gptCtx->atIndexBuffer[ptGfx->uCurrentFrameIndex], + .uIndexCount = pl_sb_size(ptDrawlist->sbtSolidIndexBuffer), + .uIndexStart = iIndexOffset, + .uInstance = 0, + .uInstanceCount = 1, + .uVertexStart = iVertexOffset + }; + + gptGfx->draw_indexed(&tEncoder, 1, &tDrawIndex); + + // bump vertex & index buffer offset + ptBufferInfo->uVertexBufferOffset += uVtxBufSzNeeded; + gptCtx->auIndexBufferOffset[ptGfx->uCurrentFrameIndex] += uIdxBufSzNeeded; + } + + // 3D lines + if(pl_sb_size(ptDrawlist->sbtLineVertexBuffer) > 0u) + { + //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~vertex buffer prep~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + // ensure gpu vertex buffer size is adequate + const uint32_t uVtxBufSzNeeded = sizeof(plDrawVertex3DLine) * pl_sb_size(ptDrawlist->sbtLineVertexBuffer); + + plBufferInfo* ptBufferInfo = &gptCtx->atLineBufferInfo[ptGfx->uCurrentFrameIndex]; + + // space left in vertex buffer + const uint32_t uAvailableVertexBufferSpace = ptBufferInfo->uVertexBufferSize - ptBufferInfo->uVertexBufferOffset; + + // grow buffer if not enough room + if(uVtxBufSzNeeded >= uAvailableVertexBufferSpace) + { + gptDevice->queue_buffer_for_deletion(&ptGfx->tDevice, ptBufferInfo->tVertexBuffer); + + const plBufferDescription tBufferDesc = { + .tUsage = PL_BUFFER_USAGE_VERTEX | PL_BUFFER_USAGE_STAGING, + .uByteSize = pl_max(ptBufferInfo->uVertexBufferSize * 2, uVtxBufSzNeeded + uAvailableVertexBufferSpace) + }; + ptBufferInfo->uVertexBufferSize = tBufferDesc.uByteSize; + + ptBufferInfo->tVertexBuffer = pl__create_staging_buffer(&tBufferDesc, "draw vtx buffer", ptGfx->uCurrentFrameIndex); + } + + // vertex GPU data transfer + plBuffer* ptVertexBuffer = gptDevice->get_buffer(&ptGfx->tDevice, ptBufferInfo->tVertexBuffer); + char* pucMappedVertexBufferLocation = ptVertexBuffer->tMemoryAllocation.pHostMapped; + memcpy(&pucMappedVertexBufferLocation[ptBufferInfo->uVertexBufferOffset], ptDrawlist->sbtLineVertexBuffer, sizeof(plDrawVertex3DLine) * pl_sb_size(ptDrawlist->sbtLineVertexBuffer)); + + //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~index buffer prep~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + // ensure gpu index buffer size is adequate + const uint32_t uIdxBufSzNeeded = sizeof(uint32_t) * pl_sb_size(ptDrawlist->sbtLineIndexBuffer); + + // space left in index buffer + const uint32_t uAvailableIndexBufferSpace = gptCtx->auIndexBufferSize[ptGfx->uCurrentFrameIndex] - gptCtx->auIndexBufferOffset[ptGfx->uCurrentFrameIndex]; + + if(uIdxBufSzNeeded >= uAvailableIndexBufferSpace) + { + + gptDevice->queue_buffer_for_deletion(&ptGfx->tDevice, gptCtx->atIndexBuffer[ptGfx->uCurrentFrameIndex]); + + const plBufferDescription tBufferDesc = { + .tUsage = PL_BUFFER_USAGE_INDEX | PL_BUFFER_USAGE_STAGING, + .uByteSize = pl_max(gptCtx->auIndexBufferSize[ptGfx->uCurrentFrameIndex] * 2, uIdxBufSzNeeded + uAvailableIndexBufferSpace) + }; + gptCtx->auIndexBufferSize[ptGfx->uCurrentFrameIndex] = tBufferDesc.uByteSize; + + gptCtx->atIndexBuffer[ptGfx->uCurrentFrameIndex] = pl__create_staging_buffer(&tBufferDesc, "draw idx buffer", ptGfx->uCurrentFrameIndex); + } + + // index GPU data transfer + plBuffer* ptIndexBuffer = gptDevice->get_buffer(&ptGfx->tDevice, gptCtx->atIndexBuffer[ptGfx->uCurrentFrameIndex]); + char* pucMappedIndexBufferLocation = ptIndexBuffer->tMemoryAllocation.pHostMapped; + memcpy(&pucMappedIndexBufferLocation[gptCtx->auIndexBufferOffset[ptGfx->uCurrentFrameIndex]], ptDrawlist->sbtLineIndexBuffer, sizeof(uint32_t) * pl_sb_size(ptDrawlist->sbtLineIndexBuffer)); + + typedef struct _plLineDynamiceData + { + plMat4 tMVP; + float fAspect; + int padding[3]; + } plLineDynamiceData; + + plDynamicBinding tLineDynamicData = gptDevice->allocate_dynamic_data(&ptGfx->tDevice, sizeof(plLineDynamiceData)); + plLineDynamiceData* ptLineDynamicData = (plLineDynamiceData*)tLineDynamicData.pcData; + ptLineDynamicData->tMVP = *ptMVP; + ptLineDynamicData->fAspect = fAspectRatio; + + gptGfx->bind_vertex_buffer(&tEncoder, ptBufferInfo->tVertexBuffer); + gptGfx->bind_shader(&tEncoder, ptEntry->tSecondaryPipeline); + gptGfx->bind_graphics_bind_groups(&tEncoder, ptEntry->tSecondaryPipeline, 0, 0, NULL, &tLineDynamicData); + + const int32_t iVertexOffset = ptBufferInfo->uVertexBufferOffset / sizeof(plDrawVertex3DLine); + const int32_t iIndexOffset = gptCtx->auIndexBufferOffset[ptGfx->uCurrentFrameIndex] / sizeof(uint32_t); + + const plDrawIndex tDrawIndex = { + .tIndexBuffer = gptCtx->atIndexBuffer[ptGfx->uCurrentFrameIndex], + .uIndexCount = pl_sb_size(ptDrawlist->sbtLineIndexBuffer), + .uIndexStart = iIndexOffset, + .uInstance = 0, + .uInstanceCount = 1, + .uVertexStart = iVertexOffset + }; + + gptGfx->draw_indexed(&tEncoder, 1, &tDrawIndex); + + // bump vertex & index buffer offset + ptBufferInfo->uVertexBufferOffset += uVtxBufSzNeeded; + gptCtx->auIndexBufferOffset[ptGfx->uCurrentFrameIndex] += uIdxBufSzNeeded; + } +} + +static void +pl__add_3d_triangle_filled(plDrawList3D* ptDrawlist, plVec3 tP0, plVec3 tP1, plVec3 tP2, plVec4 tColor) +{ + + pl_sb_reserve(ptDrawlist->sbtSolidVertexBuffer, pl_sb_size(ptDrawlist->sbtSolidVertexBuffer) + 3); + pl_sb_reserve(ptDrawlist->sbtSolidIndexBuffer, pl_sb_size(ptDrawlist->sbtSolidIndexBuffer) + 3); + + const uint32_t uVertexStart = pl_sb_size(ptDrawlist->sbtSolidVertexBuffer); + + uint32_t tU32Color = 0; + tU32Color = (uint32_t) (255.0f * tColor.r + 0.5f); + tU32Color |= (uint32_t) (255.0f * tColor.g + 0.5f) << 8; + tU32Color |= (uint32_t) (255.0f * tColor.b + 0.5f) << 16; + tU32Color |= (uint32_t) (255.0f * tColor.a + 0.5f) << 24; + + pl_sb_push(ptDrawlist->sbtSolidVertexBuffer, ((plDrawVertex3DSolid){ {tP0.x, tP0.y, tP0.z}, tU32Color})); + pl_sb_push(ptDrawlist->sbtSolidVertexBuffer, ((plDrawVertex3DSolid){ {tP1.x, tP1.y, tP1.z}, tU32Color})); + pl_sb_push(ptDrawlist->sbtSolidVertexBuffer, ((plDrawVertex3DSolid){ {tP2.x, tP2.y, tP2.z}, tU32Color})); + + pl_sb_push(ptDrawlist->sbtSolidIndexBuffer, uVertexStart + 0); + pl_sb_push(ptDrawlist->sbtSolidIndexBuffer, uVertexStart + 1); + pl_sb_push(ptDrawlist->sbtSolidIndexBuffer, uVertexStart + 2); +} + +static void +pl__add_3d_line(plDrawList3D* ptDrawlist, plVec3 tP0, plVec3 tP1, plVec4 tColor, float fThickness) +{ + uint32_t tU32Color = 0; + tU32Color = (uint32_t) (255.0f * tColor.r + 0.5f); + tU32Color |= (uint32_t) (255.0f * tColor.g + 0.5f) << 8; + tU32Color |= (uint32_t) (255.0f * tColor.b + 0.5f) << 16; + tU32Color |= (uint32_t) (255.0f * tColor.a + 0.5f) << 24; + + pl_sb_reserve(ptDrawlist->sbtLineVertexBuffer, pl_sb_size(ptDrawlist->sbtLineVertexBuffer) + 4); + pl_sb_reserve(ptDrawlist->sbtLineIndexBuffer, pl_sb_size(ptDrawlist->sbtLineIndexBuffer) + 6); + + plDrawVertex3DLine tNewVertex0 = { + {tP0.x, tP0.y, tP0.z}, + -1.0f, + fThickness, + 1.0f, + {tP1.x, tP1.y, tP1.z}, + tU32Color + }; + + plDrawVertex3DLine tNewVertex1 = { + {tP1.x, tP1.y, tP1.z}, + -1.0f, + fThickness, + -1.0f, + {tP0.x, tP0.y, tP0.z}, + tU32Color + }; + + const uint32_t uVertexStart = pl_sb_size(ptDrawlist->sbtLineVertexBuffer); + pl_sb_push(ptDrawlist->sbtLineVertexBuffer, tNewVertex0); + pl_sb_push(ptDrawlist->sbtLineVertexBuffer, tNewVertex1); + + tNewVertex0.fDirection = 1.0f; + tNewVertex1.fDirection = 1.0f; + pl_sb_push(ptDrawlist->sbtLineVertexBuffer, tNewVertex1); + pl_sb_push(ptDrawlist->sbtLineVertexBuffer, tNewVertex0); + + pl_sb_push(ptDrawlist->sbtLineIndexBuffer, uVertexStart + 0); + pl_sb_push(ptDrawlist->sbtLineIndexBuffer, uVertexStart + 1); + pl_sb_push(ptDrawlist->sbtLineIndexBuffer, uVertexStart + 2); + + pl_sb_push(ptDrawlist->sbtLineIndexBuffer, uVertexStart + 0); + pl_sb_push(ptDrawlist->sbtLineIndexBuffer, uVertexStart + 2); + pl_sb_push(ptDrawlist->sbtLineIndexBuffer, uVertexStart + 3); +} + +static void +pl__add_3d_point(plDrawList3D* ptDrawlist, plVec3 tP, plVec4 tColor, float fLength, float fThickness) +{ + const plVec3 aatVerticies[6] = { + { tP.x - fLength / 2.0f, tP.y, tP.z}, + { tP.x + fLength / 2.0f, tP.y, tP.z}, + { tP.x, tP.y - fLength / 2.0f, tP.z}, + { tP.x, tP.y + fLength / 2.0f, tP.z}, + { tP.x, tP.y, tP.z - fLength / 2.0f}, + { tP.x, tP.y, tP.z + fLength / 2.0f} + }; + + pl__add_3d_line(ptDrawlist, aatVerticies[0], aatVerticies[1], tColor, fThickness); + pl__add_3d_line(ptDrawlist, aatVerticies[2], aatVerticies[3], tColor, fThickness); + pl__add_3d_line(ptDrawlist, aatVerticies[4], aatVerticies[5], tColor, fThickness); +} + +static void +pl__add_3d_transform(plDrawList3D* ptDrawlist, const plMat4* ptTransform, float fLength, float fThickness) +{ + + const plVec3 tOrigin = pl_mul_mat4_vec3(ptTransform, (plVec3){0.0f, 0.0f, 0.0f}); + const plVec3 tXAxis = pl_mul_mat4_vec3(ptTransform, (plVec3){fLength, 0.0f, 0.0f}); + const plVec3 tYAxis = pl_mul_mat4_vec3(ptTransform, (plVec3){0.0f, fLength, 0.0f}); + const plVec3 tZAxis = pl_mul_mat4_vec3(ptTransform, (plVec3){0.0f, 0.0f, fLength}); + + pl__add_3d_line(ptDrawlist, tOrigin, tXAxis, (plVec4){1.0f, 0.0f, 0.0f, 1.0f}, fThickness); + pl__add_3d_line(ptDrawlist, tOrigin, tYAxis, (plVec4){0.0f, 1.0f, 0.0f, 1.0f}, fThickness); + pl__add_3d_line(ptDrawlist, tOrigin, tZAxis, (plVec4){0.0f, 0.0f, 1.0f, 1.0f}, fThickness); +} + +static void +pl__add_3d_frustum(plDrawList3D* ptDrawlist, const plMat4* ptTransform, float fYFov, float fAspect, float fNearZ, float fFarZ, plVec4 tColor, float fThickness) +{ + const float fSmallHeight = tanf(fYFov / 2.0f) * fNearZ; + const float fSmallWidth = fSmallHeight * fAspect; + const float fBigHeight = tanf(fYFov / 2.0f) * fFarZ; + const float fBigWidth = fBigHeight * fAspect; + + const plVec3 atVerticies[8] = { + pl_mul_mat4_vec3(ptTransform, (plVec3){ fSmallWidth, fSmallHeight, fNearZ}), + pl_mul_mat4_vec3(ptTransform, (plVec3){ fSmallWidth, -fSmallHeight, fNearZ}), + pl_mul_mat4_vec3(ptTransform, (plVec3){ -fSmallWidth, -fSmallHeight, fNearZ}), + pl_mul_mat4_vec3(ptTransform, (plVec3){ -fSmallWidth, fSmallHeight, fNearZ}), + pl_mul_mat4_vec3(ptTransform, (plVec3){ fBigWidth, fBigHeight, fFarZ}), + pl_mul_mat4_vec3(ptTransform, (plVec3){ fBigWidth, -fBigHeight, fFarZ}), + pl_mul_mat4_vec3(ptTransform, (plVec3){ -fBigWidth, -fBigHeight, fFarZ}), + pl_mul_mat4_vec3(ptTransform, (plVec3){ -fBigWidth, fBigHeight, fFarZ}) + }; + + pl__add_3d_line(ptDrawlist, atVerticies[0], atVerticies[1], tColor, fThickness); + pl__add_3d_line(ptDrawlist, atVerticies[1], atVerticies[2], tColor, fThickness); + pl__add_3d_line(ptDrawlist, atVerticies[2], atVerticies[3], tColor, fThickness); + pl__add_3d_line(ptDrawlist, atVerticies[3], atVerticies[0], tColor, fThickness); + pl__add_3d_line(ptDrawlist, atVerticies[0], atVerticies[4], tColor, fThickness); + pl__add_3d_line(ptDrawlist, atVerticies[1], atVerticies[5], tColor, fThickness); + pl__add_3d_line(ptDrawlist, atVerticies[2], atVerticies[6], tColor, fThickness); + pl__add_3d_line(ptDrawlist, atVerticies[3], atVerticies[7], tColor, fThickness); + pl__add_3d_line(ptDrawlist, atVerticies[4], atVerticies[5], tColor, fThickness); + pl__add_3d_line(ptDrawlist, atVerticies[5], atVerticies[6], tColor, fThickness); + pl__add_3d_line(ptDrawlist, atVerticies[6], atVerticies[7], tColor, fThickness); + pl__add_3d_line(ptDrawlist, atVerticies[7], atVerticies[4], tColor, fThickness); +} + +static void +pl__add_3d_centered_box(plDrawList3D* ptDrawlist, plVec3 tCenter, float fWidth, float fHeight, float fDepth, plVec4 tColor, float fThickness) +{ + const plVec3 tWidthVec = {fWidth / 2.0f, 0.0f, 0.0f}; + const plVec3 tHeightVec = {0.0f, fHeight / 2.0f, 0.0f}; + const plVec3 tDepthVec = {0.0f, 0.0f, fDepth / 2.0f}; + + const plVec3 atVerticies[8] = { + { tCenter.x - fWidth / 2.0f, tCenter.y + fHeight / 2.0f, tCenter.z - fDepth / 2.0f}, + { tCenter.x - fWidth / 2.0f, tCenter.y - fHeight / 2.0f, tCenter.z - fDepth / 2.0f}, + { tCenter.x + fWidth / 2.0f, tCenter.y - fHeight / 2.0f, tCenter.z - fDepth / 2.0f}, + { tCenter.x + fWidth / 2.0f, tCenter.y + fHeight / 2.0f, tCenter.z - fDepth / 2.0f}, + { tCenter.x - fWidth / 2.0f, tCenter.y + fHeight / 2.0f, tCenter.z + fDepth / 2.0f}, + { tCenter.x - fWidth / 2.0f, tCenter.y - fHeight / 2.0f, tCenter.z + fDepth / 2.0f}, + { tCenter.x + fWidth / 2.0f, tCenter.y - fHeight / 2.0f, tCenter.z + fDepth / 2.0f}, + { tCenter.x + fWidth / 2.0f, tCenter.y + fHeight / 2.0f, tCenter.z + fDepth / 2.0f} + }; + + pl__add_3d_line(ptDrawlist, atVerticies[0], atVerticies[1], tColor, fThickness); + pl__add_3d_line(ptDrawlist, atVerticies[1], atVerticies[2], tColor, fThickness); + pl__add_3d_line(ptDrawlist, atVerticies[2], atVerticies[3], tColor, fThickness); + pl__add_3d_line(ptDrawlist, atVerticies[3], atVerticies[0], tColor, fThickness); + pl__add_3d_line(ptDrawlist, atVerticies[0], atVerticies[4], tColor, fThickness); + pl__add_3d_line(ptDrawlist, atVerticies[1], atVerticies[5], tColor, fThickness); + pl__add_3d_line(ptDrawlist, atVerticies[2], atVerticies[6], tColor, fThickness); + pl__add_3d_line(ptDrawlist, atVerticies[3], atVerticies[7], tColor, fThickness); + pl__add_3d_line(ptDrawlist, atVerticies[4], atVerticies[5], tColor, fThickness); + pl__add_3d_line(ptDrawlist, atVerticies[5], atVerticies[6], tColor, fThickness); + pl__add_3d_line(ptDrawlist, atVerticies[6], atVerticies[7], tColor, fThickness); + pl__add_3d_line(ptDrawlist, atVerticies[7], atVerticies[4], tColor, fThickness); +} + +static void +pl__add_3d_aabb(plDrawList3D* ptDrawlist, plVec3 tMin, plVec3 tMax, plVec4 tColor, float fThickness) +{ + + const plVec3 atVerticies[] = { + { tMin.x, tMin.y, tMin.z }, + { tMax.x, tMin.y, tMin.z }, + { tMax.x, tMax.y, tMin.z }, + { tMin.x, tMax.y, tMin.z }, + { tMin.x, tMin.y, tMax.z }, + { tMax.x, tMin.y, tMax.z }, + { tMax.x, tMax.y, tMax.z }, + { tMin.x, tMax.y, tMax.z }, + }; + + pl__add_3d_line(ptDrawlist, atVerticies[0], atVerticies[1], tColor, fThickness); + pl__add_3d_line(ptDrawlist, atVerticies[1], atVerticies[2], tColor, fThickness); + pl__add_3d_line(ptDrawlist, atVerticies[2], atVerticies[3], tColor, fThickness); + pl__add_3d_line(ptDrawlist, atVerticies[3], atVerticies[0], tColor, fThickness); + pl__add_3d_line(ptDrawlist, atVerticies[0], atVerticies[4], tColor, fThickness); + pl__add_3d_line(ptDrawlist, atVerticies[1], atVerticies[5], tColor, fThickness); + pl__add_3d_line(ptDrawlist, atVerticies[2], atVerticies[6], tColor, fThickness); + pl__add_3d_line(ptDrawlist, atVerticies[3], atVerticies[7], tColor, fThickness); + pl__add_3d_line(ptDrawlist, atVerticies[4], atVerticies[5], tColor, fThickness); + pl__add_3d_line(ptDrawlist, atVerticies[5], atVerticies[6], tColor, fThickness); + pl__add_3d_line(ptDrawlist, atVerticies[6], atVerticies[7], tColor, fThickness); + pl__add_3d_line(ptDrawlist, atVerticies[7], atVerticies[4], tColor, fThickness); +} + +static void +pl__add_3d_bezier_quad(plDrawList3D* ptDrawlist, plVec3 tP0, plVec3 tP1, plVec3 tP2, plVec4 tColor, float fThickness, uint32_t uSegments) +{ + + // order of the bezier curve inputs are 0=start, 1=control, 2=ending + + if(uSegments == 0) + uSegments = 12; + + // set up first point + plVec3 atVerticies[2] = {(plVec3){0.0, 0.0, 0.0},tP0}; + + for (int i = 1; i < (int)uSegments; i++) + { + const float t = i / (float)uSegments; + const float u = 1.0f - t; + const float tt = t * t; + const float uu = u * u; + + const plVec3 p0 = pl_mul_vec3_scalarf(tP0, uu); + const plVec3 p1 = pl_mul_vec3_scalarf(tP1, (2.0f * u * t)); + const plVec3 p2 = pl_mul_vec3_scalarf(tP2, tt); + const plVec3 p3 = pl_add_vec3(p0,p1); + const plVec3 p4 = pl_add_vec3(p2,p3); + + // shift and add next point + atVerticies[0] = atVerticies[1]; + atVerticies[1] = p4; + + pl__add_3d_line(ptDrawlist, atVerticies[0], atVerticies[1], tColor, fThickness); + } + + // set up last point + atVerticies[0] = atVerticies[1]; + atVerticies[1] = tP2; + pl__add_3d_line(ptDrawlist, atVerticies[0], atVerticies[1], tColor, fThickness); +} + +static void +pl__add_3d_bezier_cubic(plDrawList3D* ptDrawlist, plVec3 tP0, plVec3 tP1, plVec3 tP2, plVec3 tP3, plVec4 tColor, float fThickness, uint32_t uSegments) +{ + // order of the bezier curve inputs are 0=start, 1=control 1, 2=control 2, 3=ending + + if(uSegments == 0) + uSegments = 12; + + // set up first point + plVec3 atVerticies[2] = {(plVec3){0.0, 0.0, 0.0},tP0}; + + for (int i = 1; i < (int)uSegments; i++) + { + const float t = i / (float)uSegments; + const float u = 1.0f - t; + const float tt = t * t; + const float uu = u * u; + const float uuu = uu * u; + const float ttt = tt * t; + + const plVec3 p0 = pl_mul_vec3_scalarf(tP0, uuu); + const plVec3 p1 = pl_mul_vec3_scalarf(tP1, (3.0f * uu * t)); + const plVec3 p2 = pl_mul_vec3_scalarf(tP2, (3.0f * u * tt)); + const plVec3 p3 = pl_mul_vec3_scalarf(tP3, (ttt)); + const plVec3 p5 = pl_add_vec3(p0,p1); + const plVec3 p6 = pl_add_vec3(p2,p3); + const plVec3 p7 = pl_add_vec3(p5,p6); + + // shift and add next point + atVerticies[0] = atVerticies[1]; + atVerticies[1] = p7; + + pl__add_3d_line(ptDrawlist, atVerticies[0], atVerticies[1], tColor, fThickness); + } + + // set up last point + atVerticies[0] = atVerticies[1]; + atVerticies[1] = tP3; + pl__add_3d_line(ptDrawlist, atVerticies[0], atVerticies[1], tColor, fThickness); +} + +//----------------------------------------------------------------------------- +// [SECTION] internal api implementation +//----------------------------------------------------------------------------- + +static plBufferHandle +pl__create_staging_buffer(const plBufferDescription* ptDesc, const char* pcName, uint32_t uIdentifier) +{ + // for convience + plDevice* ptDevice = &gptCtx->ptGraphics->tDevice; + + // create buffer + plTempAllocator tTempAllocator = {0}; + const plBufferHandle tHandle = gptDevice->create_buffer(ptDevice, ptDesc, pl_temp_allocator_sprintf(&tTempAllocator, "%s: %u", pcName, uIdentifier)); + pl_temp_allocator_reset(&tTempAllocator); + + // retrieve new buffer + plBuffer* ptBuffer = gptDevice->get_buffer(ptDevice, tHandle); + + // allocate memory + const plDeviceMemoryAllocation tAllocation = gptDevice->allocate_memory(ptDevice, + ptBuffer->tMemoryRequirements.ulSize, + PL_MEMORY_GPU_CPU, + ptBuffer->tMemoryRequirements.uMemoryTypeBits, + pl_temp_allocator_sprintf(&tTempAllocator, "%s: %u", pcName, uIdentifier)); + + // bind memory + gptDevice->bind_buffer_to_memory(ptDevice, tHandle, &tAllocation); + pl_temp_allocator_free(&tTempAllocator); + return tHandle; +} + +static const plPipelineEntry* +pl__get_3d_pipeline(plRenderPassHandle tRenderPass, uint32_t uMSAASampleCount, plDrawFlags tFlags, uint32_t uSubpassIndex) +{ + // check if pipeline exists + for(uint32_t i = 0; i < pl_sb_size(gptCtx->sbt3dPipelineEntries); i++) + { + const plPipelineEntry* ptEntry = &gptCtx->sbt3dPipelineEntries[i]; + if(ptEntry->tRenderPass.uIndex == tRenderPass.uIndex && ptEntry->uMSAASampleCount == uMSAASampleCount && ptEntry->tFlags == tFlags && ptEntry->uSubpassIndex == uSubpassIndex) + { + return ptEntry; + } + } + + pl_sb_add(gptCtx->sbt3dPipelineEntries); + plPipelineEntry* ptEntry = &gptCtx->sbt3dPipelineEntries[pl_sb_size(gptCtx->sbt3dPipelineEntries) - 1]; + ptEntry->tFlags = tFlags; + ptEntry->tRenderPass = tRenderPass; + ptEntry->uMSAASampleCount = uMSAASampleCount; + ptEntry->uSubpassIndex = uSubpassIndex; + + uint64_t ulCullMode = PL_CULL_MODE_NONE; + if(tFlags & PL_DRAW_FLAG_CULL_FRONT) + ulCullMode |= PL_CULL_MODE_CULL_FRONT; + if(tFlags & PL_DRAW_FLAG_CULL_BACK) + ulCullMode |= PL_CULL_MODE_CULL_BACK; + + const plShaderDescription t3DShaderDesc = { + + #ifdef PL_METAL_BACKEND + .pcVertexShader = "../shaders/metal/draw_3d.metal", + .pcPixelShader = "../shaders/metal/draw_3d.metal", + #else + .pcVertexShader = "draw_3d.vert.spv", + .pcPixelShader = "draw_3d.frag.spv", + #endif + .tGraphicsState = { + .ulDepthWriteEnabled = tFlags & PL_DRAW_FLAG_DEPTH_WRITE, + .ulDepthMode = tFlags & PL_DRAW_FLAG_DEPTH_TEST ? PL_COMPARE_MODE_LESS : PL_COMPARE_MODE_ALWAYS, + .ulCullMode = ulCullMode, + .ulWireframe = 0, + .ulStencilMode = PL_COMPARE_MODE_ALWAYS, + .ulStencilRef = 0xff, + .ulStencilMask = 0xff, + .ulStencilOpFail = PL_STENCIL_OP_KEEP, + .ulStencilOpDepthFail = PL_STENCIL_OP_KEEP, + .ulStencilOpPass = PL_STENCIL_OP_KEEP + }, + .tVertexBufferBinding = { + .uByteStride = sizeof(float) * 4, + .atAttributes = { + {.uByteOffset = 0, .tFormat = PL_FORMAT_R32G32B32_FLOAT}, + {.uByteOffset = sizeof(float) * 3, .tFormat = PL_FORMAT_R8G8B8A8_UNORM}, + } + }, + .uConstantCount = 0, + .atBlendStates = { + { + .bBlendEnabled = true, + .tSrcColorFactor = PL_BLEND_FACTOR_SRC_ALPHA, + .tDstColorFactor = PL_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA, + .tColorOp = PL_BLEND_OP_ADD, + .tSrcAlphaFactor = PL_BLEND_FACTOR_SRC_ALPHA, + .tDstAlphaFactor = PL_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA, + .tAlphaOp = PL_BLEND_OP_ADD + } + }, + .uBlendStateCount = 1, + .tRenderPassLayout = gptCtx->ptGraphics->sbtRenderPassesCold[tRenderPass.uIndex].tDesc.tLayout, + .uSubpassIndex = uSubpassIndex, + .uBindGroupLayoutCount = 0, + }; + + const plShaderDescription t3DLineShaderDesc = { + + #ifdef PL_METAL_BACKEND + .pcVertexShader = "../shaders/metal/draw_3d_line.metal", + .pcPixelShader = "../shaders/metal/draw_3d_line.metal", + #else + .pcVertexShader = "draw_3d_line.vert.spv", + .pcPixelShader = "draw_3d.frag.spv", + #endif + .tGraphicsState = { + .ulDepthWriteEnabled = tFlags & PL_DRAW_FLAG_DEPTH_WRITE, + .ulDepthMode = tFlags & PL_DRAW_FLAG_DEPTH_TEST ? PL_COMPARE_MODE_LESS : PL_COMPARE_MODE_ALWAYS, + .ulCullMode = ulCullMode, + .ulWireframe = 0, + .ulStencilMode = PL_COMPARE_MODE_ALWAYS, + .ulStencilRef = 0xff, + .ulStencilMask = 0xff, + .ulStencilOpFail = PL_STENCIL_OP_KEEP, + .ulStencilOpDepthFail = PL_STENCIL_OP_KEEP, + .ulStencilOpPass = PL_STENCIL_OP_KEEP + }, + .tVertexBufferBinding = { + .uByteStride = sizeof(float) * 10, + .atAttributes = { + {.uByteOffset = 0, .tFormat = PL_FORMAT_R32G32B32_FLOAT}, + {.uByteOffset = sizeof(float) * 3, .tFormat = PL_FORMAT_R32G32B32_FLOAT}, + {.uByteOffset = sizeof(float) * 6, .tFormat = PL_FORMAT_R32G32B32_FLOAT}, + {.uByteOffset = sizeof(float) * 9, .tFormat = PL_FORMAT_R8G8B8A8_UNORM}, + } + }, + .uConstantCount = 0, + .atBlendStates = { + { + .bBlendEnabled = true, + .tSrcColorFactor = PL_BLEND_FACTOR_SRC_ALPHA, + .tDstColorFactor = PL_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA, + .tColorOp = PL_BLEND_OP_ADD, + .tSrcAlphaFactor = PL_BLEND_FACTOR_SRC_ALPHA, + .tDstAlphaFactor = PL_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA, + .tAlphaOp = PL_BLEND_OP_ADD + } + }, + .uBlendStateCount = 1, + .tRenderPassLayout = gptCtx->ptGraphics->sbtRenderPassesCold[tRenderPass.uIndex].tDesc.tLayout, + .uSubpassIndex = uSubpassIndex, + .uBindGroupLayoutCount = 0, + }; + + ptEntry->tRegularPipeline = gptDevice->create_shader(&gptCtx->ptGraphics->tDevice, &t3DShaderDesc); + ptEntry->tSecondaryPipeline = gptDevice->create_shader(&gptCtx->ptGraphics->tDevice, &t3DLineShaderDesc); + return ptEntry; +} + +static const plPipelineEntry* +pl__get_2d_pipeline(plRenderPassHandle tRenderPass, uint32_t uMSAASampleCount, uint32_t uSubpassIndex) +{ + // check if pipeline exists + for(uint32_t i = 0; i < pl_sb_size(gptCtx->sbt2dPipelineEntries); i++) + { + const plPipelineEntry* ptEntry = &gptCtx->sbt2dPipelineEntries[i]; + if(ptEntry->tRenderPass.uIndex == tRenderPass.uIndex && ptEntry->uMSAASampleCount == uMSAASampleCount && ptEntry->uSubpassIndex == uSubpassIndex) + { + return ptEntry; + } + } + + pl_sb_add(gptCtx->sbt2dPipelineEntries); + plPipelineEntry* ptEntry = &gptCtx->sbt2dPipelineEntries[pl_sb_size(gptCtx->sbt2dPipelineEntries) - 1]; + ptEntry->tFlags = 0; + ptEntry->tRenderPass = tRenderPass; + ptEntry->uMSAASampleCount = uMSAASampleCount; + ptEntry->uSubpassIndex = uSubpassIndex; + + const plShaderDescription tRegularShaderDesc = { + + #ifdef PL_METAL_BACKEND + .pcVertexShader = "../shaders/metal/draw_2d.metal", + .pcPixelShader = "../shaders/metal/draw_2d.metal", + #else + .pcVertexShader = "draw_2d.vert.spv", + .pcPixelShader = "draw_2d.frag.spv", + #endif + .tGraphicsState = { + .ulDepthWriteEnabled = 0, + .ulDepthMode = PL_COMPARE_MODE_ALWAYS, + .ulCullMode = PL_CULL_MODE_NONE, + .ulWireframe = 0, + .ulStencilMode = PL_COMPARE_MODE_ALWAYS, + .ulStencilRef = 0xff, + .ulStencilMask = 0xff, + .ulStencilOpFail = PL_STENCIL_OP_KEEP, + .ulStencilOpDepthFail = PL_STENCIL_OP_KEEP, + .ulStencilOpPass = PL_STENCIL_OP_KEEP + }, + .tVertexBufferBinding = { + .uByteStride = sizeof(float) * 5, + .atAttributes = { + {.uByteOffset = 0, .tFormat = PL_FORMAT_R32G32_FLOAT}, + {.uByteOffset = sizeof(float) * 2, .tFormat = PL_FORMAT_R32G32_FLOAT}, + {.uByteOffset = sizeof(float) * 4, .tFormat = PL_FORMAT_R8G8B8A8_UNORM}, + } + }, + .uConstantCount = 0, + .atBlendStates = { + { + .bBlendEnabled = true, + .tSrcColorFactor = PL_BLEND_FACTOR_SRC_ALPHA, + .tDstColorFactor = PL_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA, + .tColorOp = PL_BLEND_OP_ADD, + .tSrcAlphaFactor = PL_BLEND_FACTOR_SRC_ALPHA, + .tDstAlphaFactor = PL_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA, + .tAlphaOp = PL_BLEND_OP_ADD + } + }, + .uBlendStateCount = 1, + .tRenderPassLayout = gptCtx->ptGraphics->sbtRenderPassesCold[tRenderPass.uIndex].tDesc.tLayout, + .uSubpassIndex = uSubpassIndex, + .uBindGroupLayoutCount = 2, + .atBindGroupLayouts = { + { + .uSamplerBindingCount = 1, + .atSamplerBindings = { + {.uSlot = 0, .tStages = PL_STAGE_PIXEL} + } + }, + { + .uTextureBindingCount = 1, + .atTextureBindings = { + {.uSlot = 0, .tStages = PL_STAGE_PIXEL, .tType = PL_TEXTURE_BINDING_TYPE_SAMPLED} + } + } + } + }; + + const plShaderDescription tSecondaryShaderDesc = { + + #ifdef PL_METAL_BACKEND + .pcVertexShader = "../shaders/metal/draw_2d_sdf.metal", + .pcPixelShader = "../shaders/metal/draw_2d_sdf.metal", + #else + .pcVertexShader = "draw_2d.vert.spv", + .pcPixelShader = "draw_2d_sdf.frag.spv", + #endif + .tGraphicsState = { + .ulDepthWriteEnabled = 0, + .ulDepthMode = PL_COMPARE_MODE_ALWAYS, + .ulCullMode = PL_CULL_MODE_NONE, + .ulWireframe = 0, + .ulStencilMode = PL_COMPARE_MODE_ALWAYS, + .ulStencilRef = 0xff, + .ulStencilMask = 0xff, + .ulStencilOpFail = PL_STENCIL_OP_KEEP, + .ulStencilOpDepthFail = PL_STENCIL_OP_KEEP, + .ulStencilOpPass = PL_STENCIL_OP_KEEP + }, + .tVertexBufferBinding = { + .uByteStride = sizeof(float) * 5, + .atAttributes = { + {.uByteOffset = 0, .tFormat = PL_FORMAT_R32G32_FLOAT}, + {.uByteOffset = sizeof(float) * 2, .tFormat = PL_FORMAT_R32G32_FLOAT}, + {.uByteOffset = sizeof(float) * 4, .tFormat = PL_FORMAT_R8G8B8A8_UNORM}, + } + }, + .uConstantCount = 0, + .atBlendStates = { + { + .bBlendEnabled = true, + .tSrcColorFactor = PL_BLEND_FACTOR_SRC_ALPHA, + .tDstColorFactor = PL_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA, + .tColorOp = PL_BLEND_OP_ADD, + .tSrcAlphaFactor = PL_BLEND_FACTOR_SRC_ALPHA, + .tDstAlphaFactor = PL_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA, + .tAlphaOp = PL_BLEND_OP_ADD + } + }, + .uBlendStateCount = 1, + .tRenderPassLayout = gptCtx->ptGraphics->sbtRenderPassesCold[tRenderPass.uIndex].tDesc.tLayout, + .uSubpassIndex = uSubpassIndex, + .uBindGroupLayoutCount = 2, + .atBindGroupLayouts = { + { + .uSamplerBindingCount = 1, + .atSamplerBindings = { + {.uSlot = 0, .tStages = PL_STAGE_PIXEL} + } + }, + { + .uTextureBindingCount = 1, + .atTextureBindings = { + {.uSlot = 0, .tStages = PL_STAGE_PIXEL, .tType = PL_TEXTURE_BINDING_TYPE_SAMPLED} + } + } + } + }; + + ptEntry->tRegularPipeline = gptDevice->create_shader(&gptCtx->ptGraphics->tDevice, &tRegularShaderDesc); + ptEntry->tSecondaryPipeline = gptDevice->create_shader(&gptCtx->ptGraphics->tDevice, &tSecondaryShaderDesc); + return ptEntry; +} + +//----------------------------------------------------------------------------- +// [SECTION] default font stuff +//----------------------------------------------------------------------------- + +static uint32_t +pl__decompress_length(const unsigned char* ptrInput) +{ + if(ptrInput) + return (ptrInput[8] << 24) + (ptrInput[9] << 16) + (ptrInput[10] << 8) + ptrInput[11]; + return 0u; +} + +static uint32_t +pl__decode85_byte(char c) { return (c >= '\\') ? c - 36 : c -35;} + +static void +pl__decode85(const unsigned char* ptrSrc, unsigned char* ptrDst) +{ + while (*ptrSrc) + { + uint32_t uTmp = pl__decode85_byte(ptrSrc[0]) + 85 * (pl__decode85_byte(ptrSrc[1]) + 85 * (pl__decode85_byte(ptrSrc[2]) + 85 * (pl__decode85_byte(ptrSrc[3]) + 85 * pl__decode85_byte(ptrSrc[4])))); + ptrDst[0] = ((uTmp >> 0) & 0xFF); + ptrDst[1] = ((uTmp >> 8) & 0xFF); + ptrDst[2] = ((uTmp >> 16) & 0xFF); + ptrDst[3] = ((uTmp >> 24) & 0xFF); + ptrSrc += 5; + ptrDst += 4; + } +} + +static unsigned char* ptrBarrierOutE_ = NULL; +static unsigned char* ptrBarrierOutB_ = NULL; +static const unsigned char * ptrBarrierInB_; +static unsigned char* ptrDOut_ = NULL; + +static void +pl__match(const unsigned char* ptrData, uint32_t uLength) +{ + PL_ASSERT(ptrDOut_ + uLength <= ptrBarrierOutE_); + if (ptrDOut_ + uLength > ptrBarrierOutE_) ptrDOut_ += uLength; + else if (ptrData < ptrBarrierOutB_) ptrDOut_ = ptrBarrierOutE_ + 1; + else while (uLength--) *ptrDOut_++ = *ptrData++; +} + +static void +pl__lit(const unsigned char* ptrData, uint32_t uLength) +{ + PL_ASSERT(ptrDOut_ + uLength <= ptrBarrierOutE_); + if (ptrDOut_ + uLength > ptrBarrierOutE_) ptrDOut_ += uLength; + else if (ptrData < ptrBarrierInB_) ptrDOut_ = ptrBarrierOutE_ + 1; + else { memcpy(ptrDOut_, ptrData, uLength); ptrDOut_ += uLength; } +} + +#define MV_IN2_(x) ((ptrI[x] << 8) + ptrI[(x) + 1]) +#define MV_IN3_(x) ((ptrI[x] << 16) + MV_IN2_((x) + 1)) +#define MV_IN4_(x) ((ptrI[x] << 24) + MV_IN3_((x) + 1)) + +static const unsigned char* +pl__decompress_token(const unsigned char* ptrI) +{ + if (*ptrI >= 0x20) + { + if (*ptrI >= 0x80) pl__match(ptrDOut_ - ptrI[1] - 1, ptrI[0] - 0x80 + 1), ptrI += 2; + else if (*ptrI >= 0x40) pl__match(ptrDOut_ - (MV_IN2_(0) - 0x4000 + 1), ptrI[2] + 1), ptrI += 3; + else /* *ptrI >= 0x20 */ pl__lit(ptrI + 1, ptrI[0] - 0x20 + 1), ptrI += 1 + (ptrI[0] - 0x20 + 1); + } + else + { + if (*ptrI >= 0x18) pl__match(ptrDOut_ - (MV_IN3_(0) - 0x180000 + 1), ptrI[3] + 1), ptrI += 4; + else if (*ptrI >= 0x10) pl__match(ptrDOut_ - (MV_IN3_(0) - 0x100000 + 1), MV_IN2_(3) + 1), ptrI += 5; + else if (*ptrI >= 0x08) pl__lit(ptrI + 2, MV_IN2_(0) - 0x0800 + 1), ptrI += 2 + (MV_IN2_(0) - 0x0800 + 1); + else if (*ptrI == 0x07) pl__lit(ptrI + 3, MV_IN2_(1) + 1), ptrI += 3 + (MV_IN2_(1) + 1); + else if (*ptrI == 0x06) pl__match(ptrDOut_ - (MV_IN3_(1) + 1), ptrI[4] + 1), ptrI += 5; + else if (*ptrI == 0x04) pl__match(ptrDOut_ - (MV_IN3_(1) + 1), MV_IN2_(4) + 1), ptrI += 6; + } + return ptrI; +} + +static uint32_t +pl__adler32(uint32_t uAdler32, unsigned char* ptrBuf, uint32_t uBufLen) +{ + const uint32_t ADLER_MOD = 65521; + uint32_t s1 = uAdler32 & 0xffff; + uint32_t s2 = uAdler32 >> 16; + uint32_t blocklen = uBufLen % 5552; + + uint32_t i = 0; + while (uBufLen) + { + for (i = 0; i + 7 < blocklen; i += 8) + { + s1 += ptrBuf[0], s2 += s1; + s1 += ptrBuf[1], s2 += s1; + s1 += ptrBuf[2], s2 += s1; + s1 += ptrBuf[3], s2 += s1; + s1 += ptrBuf[4], s2 += s1; + s1 += ptrBuf[5], s2 += s1; + s1 += ptrBuf[6], s2 += s1; + s1 += ptrBuf[7], s2 += s1; + ptrBuf += 8; + } + + for (; i < blocklen; ++i) + s1 += *ptrBuf++, s2 += s1; + + s1 %= ADLER_MOD; + s2 %= ADLER_MOD; + uBufLen -= blocklen; + blocklen = 5552; + } + return (uint32_t)(s2 << 16) + (uint32_t)s1; +} + +static uint32_t +pl__decompress(unsigned char* ptrOut, const unsigned char* ptrI, uint32_t length) +{ + if(ptrI == NULL) + return 0u; + if (MV_IN4_(0) != 0x57bC0000) + return 0u; + if (MV_IN4_(4) != 0) + return 0u; + + const uint32_t uOLen = pl__decompress_length(ptrI); + ptrBarrierInB_ = ptrI; + ptrBarrierOutE_ = ptrOut + uOLen; + ptrBarrierOutB_ = ptrOut; + ptrI += 16; + + ptrDOut_ = ptrOut; + for (;;) + { + const unsigned char* ptrOldI = ptrI; + ptrI = pl__decompress_token(ptrI); + if (ptrI == ptrOldI) + { + if (*ptrI == 0x05 && ptrI[1] == 0xfa) + { + PL_ASSERT(ptrDOut_ == ptrOut + uOLen); + if (ptrDOut_ != ptrOut + uOLen) break; + if (pl__adler32(1, ptrOut, uOLen) != (uint32_t) MV_IN4_(2))break; + return uOLen; + } + } + PL_ASSERT(ptrDOut_ <= ptrOut + uOLen); + if (ptrDOut_ > ptrOut + uOLen) + break; + } + return 0u; +} + +// File: 'ProggyClean.ttf' (41208 bytes) +static const char gcPtrDefaultFontCompressed[11980 + 1] = + "7])#######hV0qs'/###[),##/l:$#Q6>##5[n42>c-TH`->>#/e>11NNV=Bv(*:.F?uu#(gRU.o0XGH`$vhLG1hxt9?W`#,5LsCp#-i>.r$<$6pD>Lb';9Crc6tgXmKVeU2cD4Eo3R/" + "2*>]b(MC;$jPfY.;h^`IWM9Qo#t'X#(v#Y9w0#1D$CIf;W'#pWUPXOuxXuU(H9M(1=Ke$$'5F%)]0^#0X@U.a$FBjVQTSDgEKnIS7EM9>ZY9w0#L;>>#Mx&4Mvt//L[MkA#W@lK.N'[0#7RL_&#w+F%HtG9M#XL`N&.,GM4Pg;--VsM.M0rJfLH2eTM`*oJMHRC`N" + "kfimM2J,W-jXS:)r0wK#@Fge$U>`w'N7G#$#fB#$E^$#:9:hk+eOe--6x)F7*E%?76%^GMHePW-Z5l'&GiF#$956:rS?dA#fiK:)Yr+`�j@'DbG&#^$PG.Ll+DNa&VZ>1i%h1S9u5o@YaaW$e+bROPOpxTO7Stwi1::iB1q)C_=dV26J;2,]7op$]uQr@_V7$q^%lQwtuHY]=DX,n3L#0PHDO4f9>dC@O>HBuKPpP*E,N+b3L#lpR/MrTEH.IAQk.a>D[.e;mc." + "x]Ip.PH^'/aqUO/$1WxLoW0[iLAw=4h(9.`G" + "CRUxHPeR`5Mjol(dUWxZa(>STrPkrJiWx`5U7F#.g*jrohGg`cg:lSTvEY/EV_7H4Q9[Z%cnv;JQYZ5q.l7Zeas:HOIZOB?Ggv:[7MI2k).'2($5FNP&EQ(,)" + "U]W]+fh18.vsai00);D3@4ku5P?DP8aJt+;qUM]=+b'8@;mViBKx0DE[-auGl8:PJ&Dj+M6OC]O^((##]`0i)drT;-7X`=-H3[igUnPG-NZlo.#k@h#=Ork$m>a>$-?Tm$UV(?#P6YY#" + "'/###xe7q.73rI3*pP/$1>s9)W,JrM7SN]'/4C#v$U`0#V.[0>xQsH$fEmPMgY2u7Kh(G%siIfLSoS+MK2eTM$=5,M8p`A.;_R%#u[K#$x4AG8.kK/HSB==-'Ie/QTtG?-.*^N-4B/ZM" + "_3YlQC7(p7q)&](`6_c)$/*JL(L-^(]$wIM`dPtOdGA,U3:w2M-0+WomX2u7lqM2iEumMTcsF?-aT=Z-97UEnXglEn1K-bnEO`gu" + "Ft(c%=;Am_Qs@jLooI&NX;]0#j4#F14;gl8-GQpgwhrq8'=l_f-b49'UOqkLu7-##oDY2L(te+Mch&gLYtJ,MEtJfLh'x'M=$CS-ZZ%P]8bZ>#S?YY#%Q&q'3^Fw&?D)UDNrocM3A76/" + "/oL?#h7gl85[qW/NDOk%16ij;+:1a'iNIdb-ou8.P*w,v5#EI$TWS>Pot-R*H'-SEpA:g)f+O$%%`kA#G=8RMmG1&O`>to8bC]T&$,n.LoO>29sp3dt-52U%VM#q7'DHpg+#Z9%H[Ket`e;)f#Km8&+DC$I46>#Kr]]u-[=99tts1.qb#q72g1WJO81q+eN'03'eM>&1XxY-caEnO" + "j%2n8)),?ILR5^.Ibn<-X-Mq7[a82Lq:F&#ce+S9wsCK*x`569E8ew'He]h:sI[2LM$[guka3ZRd6:t%IG:;$%YiJ:Nq=?eAw;/:nnDq0(CYcMpG)qLN4$##&J-XTt,%OVU4)S1+R-#dg0/Nn?Ku1^0f$B*P:Rowwm-`0PKjYDDM'3]d39VZHEl4,.j']Pk-M.h^&:0FACm$maq-&sgw0t7/6(^xtk%" + "LuH88Fj-ekm>GA#_>568x6(OFRl-IZp`&b,_P'$MhLbxfc$mj`,O;&%W2m`Zh:/)Uetw:aJ%]K9h:TcF]u_-Sj9,VK3M.*'&0D[Ca]J9gp8,kAW]" + "%(?A%R$f<->Zts'^kn=-^@c4%-pY6qI%J%1IGxfLU9CP8cbPlXv);C=b),<2mOvP8up,UVf3839acAWAW-W?#ao/^#%KYo8fRULNd2.>%m]UK:n%r$'sw]J;5pAoO_#2mO3n,'=H5(et" + "Hg*`+RLgv>=4U8guD$I%D:W>-r5V*%j*W:Kvej.Lp$'?;++O'>()jLR-^u68PHm8ZFWe+ej8h:9r6L*0//c&iH&R8pRbA#Kjm%upV1g:" + "a_#Ur7FuA#(tRh#.Y5K+@?3<-8m0$PEn;J:rh6?I6uG<-`wMU'ircp0LaE_OtlMb&1#6T.#FDKu#1Lw%u%+GM+X'e?YLfjM[VO0MbuFp7;>Q&#WIo)0@F%q7c#4XAXN-U&VBpqB>0ie&jhZ[?iLR@@_AvA-iQC(=ksRZRVp7`.=+NpBC%rh&3]R:8XDmE5^V8O(x<-+k?'(^](H.aREZSi,#1:[IXaZFOm<-ui#qUq2$##Ri;u75OK#(RtaW-K-F`S+cF]uN`-KMQ%rP/Xri.LRcB##=YL3BgM/3M" + "D?@f&1'BW-)Ju#bmmWCMkk&#TR`C,5d>g)F;t,4:@_l8G/5h4vUd%&%950:VXD'QdWoY-F$BtUwmfe$YqL'8(PWX(" + "P?^@Po3$##`MSs?DWBZ/S>+4%>fX,VWv/w'KD`LP5IbH;rTV>n3cEK8U#bX]l-/V+^lj3;vlMb&[5YQ8#pekX9JP3XUC72L,,?+Ni&co7ApnO*5NK,((W-i:$,kp'UDAO(G0Sq7MVjJs" + "bIu)'Z,*[>br5fX^:FPAWr-m2KgLQ_nN6'8uTGT5g)uLv:873UpTLgH+#FgpH'_o1780Ph8KmxQJ8#H72L4@768@Tm&Q" + "h4CB/5OvmA&,Q&QbUoi$a_%3M01H)4x7I^&KQVgtFnV+;[Pc>[m4k//,]1?#`VY[Jr*3&&slRfLiVZJ:]?=K3Sw=[$=uRB?3xk48@aege0jT6'N#(q%.O=?2S]u*(m<-" + "V8J'(1)G][68hW$5'q[GC&5j`TE?m'esFGNRM)j,ffZ?-qx8;->g4t*:CIP/[Qap7/9'#(1sao7w-.qNUdkJ)tCF&#B^;xGvn2r9FEPFFFcL@.iFNkTve$m%#QvQS8U@)2Z+3K:AKM5i" + "sZ88+dKQ)W6>J%CL`.d*(B`-n8D9oK-XV1q['-5k'cAZ69e;D_?$ZPP&s^+7])$*$#@QYi9,5P r+$%CE=68>K8r0=dSC%%(@p7" + ".m7jilQ02'0-VWAgTlGW'b)Tq7VT9q^*^$$.:&N@@" + "$&)WHtPm*5_rO0&e%K&#-30j(E4#'Zb.o/(Tpm$>K'f@[PvFl,hfINTNU6u'0pao7%XUp9]5.>%h`8_=VYbxuel.NTSsJfLacFu3B'lQSu/m6-Oqem8T+oE--$0a/k]uj9EwsG>%veR*" + "hv^BFpQj:K'#SJ,sB-'#](j.Lg92rTw-*n%@/;39rrJF,l#qV%OrtBeC6/,;qB3ebNW[?,Hqj2L.1NP&GjUR=1D8QaS3Up&@*9wP?+lo7b?@%'k4`p0Z$22%K3+iCZj?XJN4Nm&+YF]u" + "@-W$U%VEQ/,,>>#)D#%8cY#YZ?=,`Wdxu/ae&#" + "w6)R89tI#6@s'(6Bf7a&?S=^ZI_kS&ai`&=tE72L_D,;^R)7[$so8lKN%5/$(vdfq7+ebA#" + "u1p]ovUKW&Y%q]'>$1@-[xfn$7ZTp7mM,G,Ko7a&Gu%G[RMxJs[0MM%wci.LFDK)(%:_i2B5CsR8&9Z&#=mPEnm0f`<&c)QL5uJ#%u%lJj+D-r;BoFDoS97h5g)E#o:&S4weDF,9^Hoe`h*L+_a*NrLW-1pG_&2UdB8" + "6e%B/:=>)N4xeW.*wft-;$'58-ESqr#U`'6AQ]m&6/`Z>#S?YY#Vc;r7U2&326d=w&H####?TZ`*4?&.MK?LP8Vxg>$[QXc%QJv92.(Db*B)gb*BM9dM*hJMAo*c&#" + "b0v=Pjer]$gG&JXDf->'StvU7505l9$AFvgYRI^&<^b68?j#q9QX4SM'RO#&sL1IM.rJfLUAj221]d##DW=m83u5;'bYx,*Sl0hL(W;;$doB&O/TQ:(Z^xBdLjLV#*8U_72Lh+2Q8Cj0i:6hp&$C/:p(HK>T8Y[gHQ4`4)'$Ab(Nof%V'8hL&#SfD07&6D@M.*J:;$-rv29'M]8qMv-tLp,'886iaC=Hb*YJoKJ,(j%K=H`K.v9HggqBIiZu'QvBT.#=)0ukruV&.)3=(^1`o*Pj4<-#MJ+gLq9-##@HuZPN0]u:h7.T..G:;$/Usj(T7`Q8tT72LnYl<-qx8;-HV7Q-&Xdx%1a,hC=0u+HlsV>nuIQL-5" + "_>@kXQtMacfD.m-VAb8;IReM3$wf0''hra*so568'Ip&vRs849'MRYSp%:t:h5qSgwpEr$B>Q,;s(C#$)`svQuF$##-D,##,g68@2[T;.XSdN9Qe)rpt._K-#5wF)sP'##p#C0c%-Gb%" + "hd+<-j'Ai*x&&HMkT]C'OSl##5RG[JXaHN;d'uA#x._U;.`PU@(Z3dt4r152@:v,'R.Sj'w#0<-;kPI)FfJ&#AYJ&#//)>-k=m=*XnK$>=)72L]0I%>.G690a:$##<,);?;72#?x9+d;" + "^V'9;jY@;)br#q^YQpx:X#Te$Z^'=-=bGhLf:D6&bNwZ9-ZD#n^9HhLMr5G;']d&6'wYmTFmLq9wI>P(9mI[>kC-ekLC/R&CH+s'B;K-M6$EB%is00:" + "+A4[7xks.LrNk0&E)wILYF@2L'0Nb$+pv<(2.768/FrY&h$^3i&@+G%JT'<-,v`3;_)I9M^AE]CN?Cl2AZg+%4iTpT3$U4O]GKx'm9)b@p7YsvK3w^YR-" + "CdQ*:Ir<($u&)#(&?L9Rg3H)4fiEp^iI9O8KnTj,]H?D*r7'M;PwZ9K0E^k&-cpI;.p/6_vwoFMV<->#%Xi.LxVnrU(4&8/P+:hLSKj$#U%]49t'I:rgMi'FL@a:0Y-uA[39',(vbma*" + "hU%<-SRF`Tt:542R_VV$p@[p8DV[A,?1839FWdFTi1O*H&#(AL8[_P%.M>v^-))qOT*F5Cq0`Ye%+$B6i:7@0IXSsDiWP,##P`%/L-" + "S(qw%sf/@%#B6;/U7K]uZbi^Oc^2n%t<)'mEVE''n`WnJra$^TKvX5B>;_aSEK',(hwa0:i4G?.Bci.(X[?b*($,=-n<.Q%`(X=?+@Am*Js0&=3bh8K]mL69=Lb,OcZV/);TTm8VI;?%OtJ<(b4mq7M6:u?KRdFl*:xP?Yb.5)%w_I?7uk5JC+FS(m#i'k.'a0i)9<7b'fs'59hq$*5Uhv##pi^8+hIEBF`nvo`;'l0.^S1<-wUK2/Coh58KKhLj" + "M=SO*rfO`+qC`W-On.=AJ56>>i2@2LH6A:&5q`?9I3@@'04&p2/LVa*T-4<-i3;M9UvZd+N7>b*eIwg:CC)c<>nO&#$(>.Z-I&J(Q0Hd5Q%7Co-b`-cP)hI;*_F]u`Rb[.j8_Q/<&>uu+VsH$sM9TA%?)(vmJ80),P7E>)tjD%2L=-t#fK[%`v=Q8WlA2);Sa" + ">gXm8YB`1d@K#n]76-a$U,mF%Ul:#/'xoFM9QX-$.QN'>" + "[%$Z$uF6pA6Ki2O5:8w*vP1<-1`[G,)-m#>0`P&#eb#.3i)rtB61(o'$?X3B2Qft^ae_5tKL9MUe9b*sLEQ95C&`=G?@Mj=wh*'3E>=-<)Gt*Iw)'QG:`@I" + "wOf7&]1i'S01B+Ev/Nac#9S;=;YQpg_6U`*kVY39xK,[/6Aj7:'1Bm-_1EYfa1+o&o4hp7KN_Q(OlIo@S%;jVdn0'1h19w,WQhLI)3S#f$2(eb,jr*b;3Vw]*7NH%$c4Vs,eD9>XW8?N]o+(*pgC%/72LV-uW%iewS8W6m2rtCpo'RS1R84=@paTKt)>=%&1[)*vp'u+x,VrwN;&]kuO9JDbg=pO$J*.jVe;u'm0dr9l,<*wMK*Oe=g8lV_KEBFkO'oU]^=[-792#ok,)" + "i]lR8qQ2oA8wcRCZ^7w/Njh;?.stX?Q1>S1q4Bn$)K1<-rGdO'$Wr.Lc.CG)$/*JL4tNR/,SVO3,aUw'DJN:)Ss;wGn9A32ijw%FL+Z0Fn.U9;reSq)bmI32U==5ALuG&#Vf1398/pVo" + "1*c-(aY168o<`JsSbk-,1N;$>0:OUas(3:8Z972LSfF8eb=c-;>SPw7.6hn3m`9^Xkn(r.qS[0;T%&Qc=+STRxX'q1BNk3&*eu2;&8q$&x>Q#Q7^Tf+6<(d%ZVmj2bDi%.3L2n+4W'$P" + "iDDG)g,r%+?,$@?uou5tSe2aN_AQU*'IAO" + "URQ##V^Fv-XFbGM7Fl(N<3DhLGF%q.1rC$#:T__&Pi68%0xi_&[qFJ(77j_&JWoF.V735&T,[R*:xFR*K5>>#`bW-?4Ne_&6Ne_&6Ne_&n`kr-#GJcM6X;uM6X;uM(.a..^2TkL%oR(#" + ";u.T%fAr%4tJ8&><1=GHZ_+m9/#H1F^R#SC#*N=BA9(D?v[UiFY>>^8p,KKF.W]L29uLkLlu/+4T" + "w$)F./^n3+rlo+DB;5sIYGNk+i1t-69Jg--0pao7Sm#K)pdHW&;LuDNH@H>#/X-TI(;P>#,Gc>#0Su>#4`1?#8lC?#xL$#B.`$#F:r$#JF.%#NR@%#R_R%#Vke%#Zww%#_-4^Rh%Sflr-k'MS.o?.5/sWel/wpEM0%3'/1)K^f1-d>G21&v(35>V`39V7A4=onx4" + "A1OY5EI0;6Ibgr6M$HS7Q<)58C5w,;WoA*#[%T*#`1g*#d=#+#hI5+#lUG+#pbY+#tnl+#x$),#&1;,#*=M,#.I`,#2Ur,#6b.-#;w[H#iQtA#m^0B#qjBB#uvTB##-hB#'9$C#+E6C#" + "/QHC#3^ZC#7jmC#;v)D#?,)4kMYD4lVu`4m`:&5niUA5@(A5BA1]PBB:xlBCC=2CDLXMCEUtiCf&0g2'tN?PGT4CPGT4CPGT4CPGT4CPGT4CPGT4CPGT4CP" + "GT4CPGT4CPGT4CPGT4CPGT4CPGT4CP-qekC`.9kEg^+F$kwViFJTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5KTB&5o,^<-28ZI'O?;xp" + "O?;xpO?;xpO?;xpO?;xpO?;xpO?;xpO?;xpO?;xpO?;xpO?;xpO?;xpO?;xpO?;xp;7q-#lLYI:xvD=#"; + +static void +pl_add_default_font(plFontAtlas* ptrAtlas) +{ + static const char* cPtrEmbeddedFontName = "Proggy.ttf"; + + void* data = NULL; + + int iCompressedTTFSize = (((int)strlen(gcPtrDefaultFontCompressed) + 4) / 5) * 4; + void* ptrCompressedTTF = PL_ALLOC((size_t)iCompressedTTFSize); + pl__decode85((const unsigned char*)gcPtrDefaultFontCompressed, (unsigned char*)ptrCompressedTTF); + + const uint32_t uDecompressedSize = pl__decompress_length((const unsigned char*)ptrCompressedTTF); + data = (unsigned char*)PL_ALLOC(uDecompressedSize); + pl__decompress((unsigned char*)data, (const unsigned char*)ptrCompressedTTF, (int)iCompressedTTFSize); + + PL_FREE(ptrCompressedTTF); + + plFontConfig fontConfig = { + .bSdf = false, + .fFontSize = 13.0f, + .uHOverSampling = 1, + .uVOverSampling = 1, + .ucOnEdgeValue = 255, + .iSdfPadding = 1 + }; + + plFontRange range = { + .iFirstCodePoint = 0x0020, + .uCharCount = 0x00FF - 0x0020 + }; + pl_sb_push(fontConfig.sbtRanges, range); + pl_add_font_from_memory_ttf(ptrAtlas, fontConfig, data); +} + +//----------------------------------------------------------------------------- +// [SECTION] extension loading +//----------------------------------------------------------------------------- + +static const plDrawI* +pl_load_draw_3d_api(void) +{ + static const plDrawI tApi = { + .initialize = pl_initialize, + .cleanup = pl_cleanup, + .request_3d_drawlist = pl_request_3d_drawlist, + .return_3d_drawlist = pl_return_3d_drawlist, + .submit_2d_drawlist = pl__submit_2d_drawlist, + .submit_3d_drawlist = pl__submit_3d_drawlist, + .new_frame = pl_new_draw_3d_frame, + .add_3d_triangle_filled = pl__add_3d_triangle_filled, + .add_3d_line = pl__add_3d_line, + .add_3d_point = pl__add_3d_point, + .add_3d_transform = pl__add_3d_transform, + .add_3d_frustum = pl__add_3d_frustum, + .add_3d_centered_box = pl__add_3d_centered_box, + .add_3d_bezier_quad = pl__add_3d_bezier_quad, + .add_3d_bezier_cubic = pl__add_3d_bezier_cubic, + .add_3d_aabb = pl__add_3d_aabb, + .request_2d_drawlist = pl_request_2d_drawlist, + .return_2d_drawlist = pl_return_2d_drawlist, + .request_2d_layer = pl_request_2d_layer, + .return_2d_layer = pl_return_2d_layer, + .submit_2d_layer = pl_submit_2d_layer, + .build_font_atlas = pl_build_font_atlas, + .cleanup_font_atlas = pl_cleanup_font_atlas, + .add_default_font = pl_add_default_font, + .add_font_from_file_ttf = pl_add_font_from_file_ttf, + .add_font_from_memory_ttf = pl_add_font_from_memory_ttf, + .calculate_text_size = pl_calculate_text_size, + .calculate_text_size_ex = pl_calculate_text_size_ex, + .calculate_text_bb = pl_calculate_text_bb, + .calculate_text_bb_ex = pl_calculate_text_bb_ex, + .push_clip_rect_pt = pl_push_clip_rect_pt, + .push_clip_rect = pl_push_clip_rect, + .pop_clip_rect = pl_pop_clip_rect, + .get_clip_rect = pl_get_clip_rect, + .add_line = pl_add_line, + .add_lines = pl_add_lines, + .add_text = pl_add_text, + .add_text_ex = pl_add_text_ex, + .add_text_clipped = pl_add_text_clipped, + .add_text_clipped_ex = pl_add_text_clipped_ex, + .add_triangle = pl_add_triangle, + .add_triangle_filled = pl_add_triangle_filled, + .add_rect = pl_add_rect, + .add_rect_filled = pl_add_rect_filled, + .add_rect_rounded = pl_add_rect_rounded, + .add_rect_rounded_filled = pl_add_rect_rounded_filled, + .add_quad = pl_add_quad, + .add_quad_filled = pl_add_quad_filled, + .add_circle = pl_add_circle, + .add_circle_filled = pl_add_circle_filled, + .add_image = pl_add_image, + .add_image_ex = pl_add_image_ex, + .add_bezier_quad = pl_add_bezier_quad, + .add_bezier_cubic = pl_add_bezier_cubic, + + }; + return &tApi; +} + +PL_EXPORT void +pl_load_ext(plApiRegistryI* ptApiRegistry, bool bReload) +{ + const plDataRegistryI* ptDataRegistry = ptApiRegistry->first(PL_API_DATA_REGISTRY); + pl_set_memory_context(ptDataRegistry->get_data(PL_CONTEXT_MEMORY)); + + gptDevice = ptApiRegistry->first(PL_API_DEVICE); + gptGfx = ptApiRegistry->first(PL_API_GRAPHICS); + if(bReload) + { + gptCtx = ptDataRegistry->get_data("plDrawContext"); + ptApiRegistry->replace(ptApiRegistry->first(PL_API_DRAW), pl_load_draw_3d_api()); + } + else + { + ptApiRegistry->add(PL_API_DRAW, pl_load_draw_3d_api()); + + static plDrawContext tCtx = {0}; + gptCtx = &tCtx; + ptDataRegistry->set_data("plDrawContext", gptCtx); + } +} + +PL_EXPORT void +pl_unload_ext(plApiRegistryI* ptApiRegistry) +{ +} + +//----------------------------------------------------------------------------- +// [SECTION] unity build +//----------------------------------------------------------------------------- + +#define STB_RECT_PACK_IMPLEMENTATION +#include "stb_rect_pack.h" +#undef STB_RECT_PACK_IMPLEMENTATION + +#define STB_TRUETYPE_IMPLEMENTATION +#include "stb_truetype.h" +#undef STB_TRUETYPE_IMPLEMENTATION \ No newline at end of file diff --git a/extensions/pl_draw_ext.h b/extensions/pl_draw_ext.h new file mode 100644 index 00000000..f6316664 --- /dev/null +++ b/extensions/pl_draw_ext.h @@ -0,0 +1,270 @@ +/* + pl_draw_ext.h +*/ + +/* +Index of this file: +// [SECTION] header mess +// [SECTION] defines +// [SECTION] apis +// [SECTION] includes +// [SECTION] forward declarations & basic types +// [SECTION] public api struct +// [SECTION] enums +// [SECTION] structs +*/ + +//----------------------------------------------------------------------------- +// [SECTION] header mess +//----------------------------------------------------------------------------- + +#ifndef PL_DRAW_EXT_H +#define PL_DRAW_EXT_H + +#define PL_DRAW_EXT_VERSION "0.1.0" +#define PL_DRAW_EXT_VERSION_NUM 000100 + +//----------------------------------------------------------------------------- +// [SECTION] apis +//----------------------------------------------------------------------------- + +#define PL_API_DRAW "PL_API_DRAW" +typedef struct _plDrawI plDrawI; + +//----------------------------------------------------------------------------- +// [SECTION] defines +//----------------------------------------------------------------------------- + +#ifndef PL_MAX_DRAWLISTS + #define PL_MAX_DRAWLISTS 64 +#endif + +//----------------------------------------------------------------------------- +// [SECTION] includes +//----------------------------------------------------------------------------- + +#include +#include "pl_math.h" + +#include "pl_graphics_ext.h" + +//----------------------------------------------------------------------------- +// [SECTION] forward declarations & basic types +//----------------------------------------------------------------------------- + +// basic types +typedef struct _plDrawList2D plDrawList2D; +typedef struct _plDrawList3D plDrawList3D; +typedef struct _plDrawLayer2D plDrawLayer2D; + +// font types +typedef struct _plFontChar plFontChar; // internal for now (opaque structure) +typedef struct _plFontGlyph plFontGlyph; // internal for now (opaque structure) +typedef struct _plFontCustomRect plFontCustomRect; // internal for now (opaque structure) +typedef struct _plFontPrepData plFontPrepData; // internal for now (opaque structure) +typedef struct _plFontRange plFontRange; // a range of characters +typedef struct _plFont plFont; // a single font with a specific size and config +typedef struct _plFontConfig plFontConfig; // configuration for loading a single font +typedef struct _plFontAtlas plFontAtlas; // atlas for multiple fonts + +// enums +typedef int plDrawFlags; + +// external +typedef struct _plGraphics plGraphics; // pl_graphics_ext.h +typedef struct _plRenderEncoder plRenderEncoder; // pl_graphics_ext.h +typedef union plTextureHandle plTextureHandle; // pl_graphics_ext.h + +//----------------------------------------------------------------------------- +// [SECTION] public api struct +//----------------------------------------------------------------------------- + +typedef struct _plDrawI +{ + // init/cleanup + void (*initialize)(plGraphics*); + void (*cleanup) (void); + + // per frame + void (*new_frame)(void); + + //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~fonts~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + void (*build_font_atlas) (plFontAtlas*); + void (*cleanup_font_atlas) (plFontAtlas*); + void (*add_default_font) (plFontAtlas*); + void (*add_font_from_file_ttf) (plFontAtlas*, plFontConfig, const char* pcFile); + void (*add_font_from_memory_ttf)(plFontAtlas*, plFontConfig, void* pData); + plVec2 (*calculate_text_size) (plFont*, float fSize, const char* pcText, float fWrap); + plVec2 (*calculate_text_size_ex) (plFont*, float fSize, const char* pcText, const char* pcTextEnd, float fWrap); + plRect (*calculate_text_bb) (plFont*, float fSize, plVec2 tP, const char* pcText, float fWrap); + plRect (*calculate_text_bb_ex) (plFont*, float fSize, plVec2 tP, const char* pcText, const char* pcTextEnd, float fWrap); + + //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~2D~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + // drawlists + plDrawList2D* (*request_2d_drawlist)(void); + void (*return_2d_drawlist) (plDrawList2D*); + void (*submit_2d_drawlist)(plDrawList2D*, plRenderEncoder, float fWidth, float fHeight, uint32_t uMSAASampleCount); + + // layers + plDrawLayer2D* (*request_2d_layer) (plDrawList2D*, const char* pcName); + void (*return_2d_layer) (plDrawLayer2D*); + void (*submit_2d_layer) (plDrawLayer2D*); + + // drawing + void (*add_line) (plDrawLayer2D*, plVec2 tP0, plVec2 tP1, plVec4 tColor, float fThickness); + void (*add_lines) (plDrawLayer2D*, plVec2* atPoints, uint32_t uCount, plVec4 tColor, float fThickness); + void (*add_text) (plDrawLayer2D*, plFont*, float fSize, plVec2 tP, plVec4 tColor, const char* pcText, float fWrap); + void (*add_text_ex) (plDrawLayer2D*, plFont*, float fSize, plVec2 tP, plVec4 tColor, const char* pcText, const char* pcTextEnd, float fWrap); + void (*add_text_clipped) (plDrawLayer2D*, plFont*, float fSize, plVec2 tP, plVec2 tMin, plVec2 tMax, plVec4 tColor, const char* pcText, float fWrap); + void (*add_text_clipped_ex) (plDrawLayer2D*, plFont*, float fSize, plVec2 tP, plVec2 tMin, plVec2 tMax, plVec4 tColor, const char* pcText, const char* pcTextEnd, float fWrap); + void (*add_triangle) (plDrawLayer2D*, plVec2 tP0, plVec2 tP1, plVec2 tP2, plVec4 tColor, float fThickness); + void (*add_triangle_filled) (plDrawLayer2D*, plVec2 tP0, plVec2 tP1, plVec2 tP2, plVec4 tColor); + void (*add_rect) (plDrawLayer2D*, plVec2 tMinP, plVec2 tMaxP, plVec4 tColor, float fThickness); + void (*add_rect_filled) (plDrawLayer2D*, plVec2 tMinP, plVec2 tMaxP, plVec4 tColor); + void (*add_rect_rounded) (plDrawLayer2D*, plVec2 tMinP, plVec2 tMaxP, plVec4 tColor, float fThickness, float fRadius, uint32_t uSegments); + void (*add_rect_rounded_filled)(plDrawLayer2D*, plVec2 tMinP, plVec2 tMaxP, plVec4 tColor, float fRadius, uint32_t uSegments); + void (*add_quad) (plDrawLayer2D*, plVec2 tP0, plVec2 tP1, plVec2 tP2, plVec2 tP3, plVec4 tColor, float fThickness); + void (*add_quad_filled) (plDrawLayer2D*, plVec2 tP0, plVec2 tP1, plVec2 tP2, plVec2 tP3, plVec4 tColor); + void (*add_circle) (plDrawLayer2D*, plVec2 tP, float fRadius, plVec4 tColor, uint32_t uSegments, float fThickness); + void (*add_circle_filled) (plDrawLayer2D*, plVec2 tP, float fRadius, plVec4 tColor, uint32_t uSegments); + void (*add_image) (plDrawLayer2D*, plTextureHandle, plVec2 tPMin, plVec2 tPMax); + void (*add_image_ex) (plDrawLayer2D*, plTextureHandle, plVec2 tPMin, plVec2 tPMax, plVec2 tUvMin, plVec2 tUvMax, plVec4 tColor); + void (*add_bezier_quad) (plDrawLayer2D*, plVec2 tP0, plVec2 tP1, plVec2 tP2, plVec4 tColor, float fThickness, uint32_t uSegments); + void (*add_bezier_cubic) (plDrawLayer2D*, plVec2 tP0, plVec2 tP1, plVec2 tP2, plVec2 tP3, plVec4 tColor, float fThickness, uint32_t uSegments); + + // clipping + void (*push_clip_rect_pt)(plDrawList2D*, const plRect*); + void (*push_clip_rect) (plDrawList2D*, plRect, bool bAccumulate); + void (*pop_clip_rect) (plDrawList2D*); + const plRect* (*get_clip_rect) (plDrawList2D*); + + //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~2D~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + // drawlists + plDrawList3D* (*request_3d_drawlist)(void); + void (*return_3d_drawlist)(plDrawList3D*); + void (*submit_3d_drawlist)(plDrawList3D*, plRenderEncoder, float fWidth, float fHeight, const plMat4* ptMVP, plDrawFlags, uint32_t uMSAASampleCount); + + // drawing + void (*add_3d_triangle_filled)(plDrawList3D*, plVec3 tP0, plVec3 tP1, plVec3 tP2, plVec4 tColor); + void (*add_3d_line) (plDrawList3D*, plVec3 tP0, plVec3 tP1, plVec4 tColor, float fThickness); + void (*add_3d_point) (plDrawList3D*, plVec3 tP0, plVec4 tColor, float fLength, float fThickness); + void (*add_3d_transform) (plDrawList3D*, const plMat4* ptTransform, float fLength, float fThickness); + void (*add_3d_frustum) (plDrawList3D*, const plMat4* ptTransform, float fYFov, float fAspect, float fNearZ, float fFarZ, plVec4 tColor, float fThickness); + void (*add_3d_centered_box) (plDrawList3D*, plVec3 tCenter, float fWidth, float fHeight, float fDepth, plVec4 tColor, float fThickness); + void (*add_3d_aabb) (plDrawList3D*, plVec3 tMin, plVec3 tMax, plVec4 tColor, float fThickness); + void (*add_3d_bezier_quad) (plDrawList3D*, plVec3 tP0, plVec3 tP1, plVec3 tP2, plVec4 tColor, float fThickness, uint32_t uSegments); + void (*add_3d_bezier_cubic) (plDrawList3D*, plVec3 tP0, plVec3 tP1, plVec3 tP2, plVec3 tP3, plVec4 tColor, float fThickness, uint32_t uSegments); + +} plDrawI; + +//----------------------------------------------------------------------------- +// [SECTION] enums +//----------------------------------------------------------------------------- + +enum _plDrawFlags +{ + PL_DRAW_FLAG_NONE = 0, + PL_DRAW_FLAG_DEPTH_TEST = 1 << 0, + PL_DRAW_FLAG_DEPTH_WRITE = 1 << 1, + PL_DRAW_FLAG_CULL_FRONT = 1 << 2, + PL_DRAW_FLAG_CULL_BACK = 1 << 3, + PL_DRAW_FLAG_FRONT_FACE_CW = 1 << 4, +}; + +//----------------------------------------------------------------------------- +// [SECTION] structs +//----------------------------------------------------------------------------- + +typedef struct _plFontRange +{ + int iFirstCodePoint; + uint32_t uCharCount; + plFontChar* ptFontChar; // offset into parent font's char data +} plFontRange; + +typedef struct _plFontConfig +{ + float fFontSize; + plFontRange* sbtRanges; + int* sbiIndividualChars; + + // BITMAP + uint32_t uVOverSampling; + uint32_t uHOverSampling; + + // SDF + bool bSdf; + int iSdfPadding; + unsigned char ucOnEdgeValue; + float fSdfPixelDistScale; +} plFontConfig; + +typedef struct _plFont +{ + plFontConfig tConfig; + plFontAtlas* ptParentAtlas; + float fLineSpacing; + float fAscent; + float fDescent; + + uint32_t* sbuCodePoints; // glyph index lookup based on codepoint + plFontGlyph* sbtGlyphs; // glyphs + plFontChar* sbtCharData; +} plFont; + +typedef struct _plFontAtlas +{ + plFont* sbtFonts; + plFontCustomRect* sbtCustomRects; + unsigned char* pucPixelsAsAlpha8; + unsigned char* pucPixelsAsRGBA32; + uint32_t auAtlasSize[2]; + float afWhiteUv[2]; + bool bDirty; + int iGlyphPadding; + size_t szPixelDataSize; + plFontCustomRect* ptWhiteRect; + plTextureHandle tTexture; + plFontPrepData* _sbtPrepData; +} plFontAtlas; + +typedef struct _plFontCustomRect +{ + uint32_t uWidth; + uint32_t uHeight; + uint32_t uX; + uint32_t uY; + unsigned char* pucBytes; +} plFontCustomRect; + +typedef struct _plFontChar +{ + uint16_t x0; + uint16_t y0; + uint16_t x1; + uint16_t y1; + float xOff; + float yOff; + float xAdv; + float xOff2; + float yOff2; +} plFontChar; + +typedef struct _plFontGlyph +{ + float x0; + float y0; + float u0; + float v0; + float x1; + float y1; + float u1; + float v1; + float xAdvance; + float leftBearing; +} plFontGlyph; + +#endif // PL_DRAW_EXT_H \ No newline at end of file diff --git a/extensions/pl_gpu_allocators_ext.c b/extensions/pl_gpu_allocators_ext.c index 6db75e0f..f56a09b2 100644 --- a/extensions/pl_gpu_allocators_ext.c +++ b/extensions/pl_gpu_allocators_ext.c @@ -32,7 +32,7 @@ typedef struct _plDeviceAllocatorData { plDeviceMemoryAllocatorI* ptAllocator; plDevice* ptDevice; - plDeviceAllocationBlock* sbtBlocks; + plDeviceMemoryAllocation* sbtBlocks; uint32_t* sbtFreeBlockIndices; // buddy allocator data @@ -40,7 +40,7 @@ typedef struct _plDeviceAllocatorData uint32_t auFreeList[PL_DEVICE_LOCAL_LEVELS]; } plDeviceAllocatorData; -static plDeviceAllocationBlock* +static plDeviceMemoryAllocation* pl_get_allocator_blocks(const plDeviceMemoryAllocatorI* ptAllocator, uint32_t* puSizeOut) { plDeviceAllocatorData* ptData = (plDeviceAllocatorData*)ptAllocator->ptInst; @@ -112,8 +112,8 @@ pl__create_device_node(struct plDeviceMemoryAllocatorO* ptInst, uint32_t uMemory plDeviceAllocatorData* ptData = (plDeviceAllocatorData*)ptInst; uint32_t uNode = UINT32_MAX; - plDeviceAllocationBlock tBlock = { - .ulAddress = 0, + plDeviceMemoryAllocation tBlock = { + .uHandle = 0, .ulSize = PL_DEVICE_BUDDY_BLOCK_SIZE, .ulMemoryType = uMemoryType }; @@ -364,11 +364,11 @@ pl_allocate_dedicated(struct plDeviceMemoryAllocatorO* ptInst, uint32_t uTypeFil { plDeviceAllocatorData* ptData = (plDeviceAllocatorData*)ptInst; - plDeviceAllocationBlock tBlock = gptDevice->allocate_memory(ptData->ptDevice, ulSize, PL_MEMORY_GPU, uTypeFilter, pcName); + plDeviceMemoryAllocation tBlock = gptDevice->allocate_memory(ptData->ptDevice, ulSize, PL_MEMORY_GPU, uTypeFilter, pcName); plDeviceMemoryAllocation tAllocation = { .pHostMapped = NULL, - .uHandle = tBlock.ulAddress, + .uHandle = tBlock.uHandle, .ulOffset = 0, .ulSize = ulSize, .ptAllocator = ptData->ptAllocator, @@ -404,9 +404,9 @@ pl_free_dedicated(struct plDeviceMemoryAllocatorO* ptInst, plDeviceMemoryAllocat for(uint32_t i = 0; i < pl_sb_size(ptData->sbtNodes); i++) { plDeviceAllocationRange* ptNode = &ptData->sbtNodes[i]; - plDeviceAllocationBlock* ptBlock = &ptData->sbtBlocks[ptNode->ulBlockIndex]; + plDeviceMemoryAllocation* ptBlock = &ptData->sbtBlocks[ptNode->ulBlockIndex]; - if(ptBlock->ulAddress == ptAllocation->uHandle) + if(ptBlock->uHandle == ptAllocation->uHandle) { uNodeIndex = i; uBlockIndex = (uint32_t)ptNode->ulBlockIndex; @@ -458,11 +458,11 @@ pl_allocate_buddy(struct plDeviceMemoryAllocatorO* ptInst, uint32_t uTypeFilter, ptNode->ulUsedSize = ulSize; const uint32_t uBlockCount = pl_sb_size(ptData->sbtBlocks); - plDeviceAllocationBlock* ptBlock = &ptData->sbtBlocks[ptNode->ulBlockIndex]; + plDeviceMemoryAllocation* ptBlock = &ptData->sbtBlocks[ptNode->ulBlockIndex]; plDeviceMemoryAllocation tAllocation = { .pHostMapped = NULL, - .uHandle = (uint64_t)ptBlock->ulAddress, + .uHandle = (uint64_t)ptBlock->uHandle, .ulOffset = ptNode->ulOffset, .ulSize = ulSize, .ptAllocator = ptData->ptAllocator, @@ -474,8 +474,8 @@ pl_allocate_buddy(struct plDeviceMemoryAllocatorO* ptInst, uint32_t uTypeFilter, if(tAllocation.uHandle == 0) { - ptBlock->ulAddress = gptDevice->allocate_memory(ptData->ptDevice, PL_DEVICE_BUDDY_BLOCK_SIZE, PL_MEMORY_GPU, uTypeFilter, "Buddy Heap").ulAddress; - tAllocation.uHandle = (uint64_t)ptBlock->ulAddress; + ptBlock->uHandle = gptDevice->allocate_memory(ptData->ptDevice, PL_DEVICE_BUDDY_BLOCK_SIZE, PL_MEMORY_GPU, uTypeFilter, "Buddy Heap").uHandle; + tAllocation.uHandle = (uint64_t)ptBlock->uHandle; } return tAllocation; @@ -492,9 +492,9 @@ pl_free_buddy(struct plDeviceMemoryAllocatorO* ptInst, plDeviceMemoryAllocation* for(uint32_t i = 0; i < pl_sb_size(ptData->sbtNodes); i++) { plDeviceAllocationRange* ptIntermediateNode = &ptData->sbtNodes[i]; - plDeviceAllocationBlock* ptBlock = &ptData->sbtBlocks[ptIntermediateNode->ulBlockIndex]; + plDeviceMemoryAllocation* ptBlock = &ptData->sbtBlocks[ptIntermediateNode->ulBlockIndex]; - if(ptBlock->ulAddress == ptAllocation->uHandle && ptIntermediateNode->ulOffset == ptAllocation->ulOffset && ptIntermediateNode->ulUsedSize == ptAllocation->ulSize) + if(ptBlock->uHandle == ptAllocation->uHandle && ptIntermediateNode->ulOffset == ptAllocation->ulOffset && ptIntermediateNode->ulUsedSize == ptAllocation->ulSize) { ptNode = &ptData->sbtNodes[i]; uNodeIndex = (uint32_t)i; @@ -522,11 +522,11 @@ pl_allocate_staging_uncached(struct plDeviceMemoryAllocatorO* ptInst, uint32_t u { plDeviceAllocatorData* ptData = (plDeviceAllocatorData*)ptInst; - plDeviceAllocationBlock tBlock = gptDevice->allocate_memory(ptData->ptDevice, ulSize, PL_MEMORY_GPU_CPU, uTypeFilter, pcName); + plDeviceMemoryAllocation tBlock = gptDevice->allocate_memory(ptData->ptDevice, ulSize, PL_MEMORY_GPU_CPU, uTypeFilter, pcName); plDeviceMemoryAllocation tAllocation = { .pHostMapped = tBlock.pHostMapped, - .uHandle = tBlock.ulAddress, + .uHandle = tBlock.uHandle, .ulOffset = 0, .ulSize = ulSize, .ptAllocator = ptData->ptAllocator, @@ -562,9 +562,9 @@ pl_free_staging_uncached(struct plDeviceMemoryAllocatorO* ptInst, plDeviceMemory for(uint32_t i = 0; i < pl_sb_size(ptData->sbtNodes); i++) { plDeviceAllocationRange* ptNode = &ptData->sbtNodes[i]; - plDeviceAllocationBlock* ptBlock = &ptData->sbtBlocks[ptNode->ulBlockIndex]; + plDeviceMemoryAllocation* ptBlock = &ptData->sbtBlocks[ptNode->ulBlockIndex]; - if(ptBlock->ulAddress == ptAllocation->uHandle) + if(ptBlock->uHandle == ptAllocation->uHandle) { uNodeIndex = i; uBlockIndex = (uint32_t)ptNode->ulBlockIndex; @@ -637,7 +637,7 @@ pl_cleanup_allocators(plDevice* ptDevice) for(uint32_t i = 0; i < pl_sb_size(ptAllocatorData->sbtBlocks); i++) { - if(ptAllocatorData->sbtBlocks[i].ulAddress) + if(ptAllocatorData->sbtBlocks[i].uHandle) gptDevice->free_memory(ptDevice, &ptAllocatorData->sbtBlocks[i]); } pl_sb_free(ptAllocatorData->sbtBlocks); @@ -648,7 +648,7 @@ pl_cleanup_allocators(plDevice* ptDevice) ptAllocatorData = (plDeviceAllocatorData*)ptAllocator->ptInst; for(uint32_t i = 0; i < pl_sb_size(ptAllocatorData->sbtBlocks); i++) { - if(ptAllocatorData->sbtBlocks[i].ulAddress) + if(ptAllocatorData->sbtBlocks[i].uHandle) gptDevice->free_memory(ptDevice, &ptAllocatorData->sbtBlocks[i]); } pl_sb_free(ptAllocatorData->sbtBlocks); @@ -659,7 +659,7 @@ pl_cleanup_allocators(plDevice* ptDevice) ptAllocatorData = (plDeviceAllocatorData*)ptAllocator->ptInst; for(uint32_t i = 0; i < pl_sb_size(ptAllocatorData->sbtBlocks); i++) { - if(ptAllocatorData->sbtBlocks[i].ulAddress) + if(ptAllocatorData->sbtBlocks[i].uHandle) gptDevice->free_memory(ptDevice, &ptAllocatorData->sbtBlocks[i]); } pl_sb_free(ptAllocatorData->sbtBlocks); diff --git a/extensions/pl_gpu_allocators_ext.h b/extensions/pl_gpu_allocators_ext.h index ba050471..0904c739 100644 --- a/extensions/pl_gpu_allocators_ext.h +++ b/extensions/pl_gpu_allocators_ext.h @@ -49,8 +49,8 @@ Index of this file: //----------------------------------------------------------------------------- // basic types -typedef struct _plDeviceAllocationRange plDeviceAllocationRange; -typedef struct _plDeviceAllocationBlock plDeviceAllocationBlock; +typedef struct _plDeviceAllocationRange plDeviceAllocationRange; +typedef struct _plDeviceMemoryAllocation plDeviceMemoryAllocation; // external (pl_graphics_ext.h) typedef struct _plDeviceMemoryAllocatorI plDeviceMemoryAllocatorI; @@ -77,8 +77,8 @@ typedef struct _plGPUAllocatorsI void (*cleanup_allocators)(plDevice*); // for debug viewing - plDeviceAllocationBlock* (*get_blocks)(const plDeviceMemoryAllocatorI*, uint32_t* puSizeOut); - plDeviceAllocationRange* (*get_ranges)(const plDeviceMemoryAllocatorI*, uint32_t* puSizeOut); + plDeviceMemoryAllocation* (*get_blocks)(const plDeviceMemoryAllocatorI*, uint32_t* puSizeOut); + plDeviceAllocationRange* (*get_ranges)(const plDeviceMemoryAllocatorI*, uint32_t* puSizeOut); } plGPUAllocatorsI; diff --git a/extensions/pl_graphics_ext.c b/extensions/pl_graphics_ext.c index 74b92f1f..cf5e62b7 100644 --- a/extensions/pl_graphics_ext.c +++ b/extensions/pl_graphics_ext.c @@ -394,6 +394,7 @@ pl__cleanup_common_graphics(plGraphics* ptGraphics) } pl_sb_free(ptGraphics->sbtGarbage); + pl_sb_free(ptGraphics->sbtFreeDrawBindGroups); pl_sb_free(ptGraphics->tSwapchain.sbtSwapchainTextureViews); pl_sb_free(ptGraphics->sbtShadersCold); pl_sb_free(ptGraphics->sbtBuffersCold); diff --git a/extensions/pl_graphics_ext.h b/extensions/pl_graphics_ext.h index 20574e87..649a5f7e 100644 --- a/extensions/pl_graphics_ext.h +++ b/extensions/pl_graphics_ext.h @@ -179,7 +179,6 @@ PL_DEFINE_HANDLE(plSemaphoreHandle); // device memory typedef struct _plDeviceMemoryRequirements plDeviceMemoryRequirements; -typedef struct _plDeviceAllocationBlock plDeviceAllocationBlock; typedef struct _plDeviceMemoryAllocation plDeviceMemoryAllocation; typedef struct _plDeviceMemoryAllocatorI plDeviceMemoryAllocatorI; @@ -207,9 +206,7 @@ typedef int plBlendFactor; // -> enum _plBlendFactor // Enum: typedef int plMipmapMode; // -> enum _plMipmapMode // Enum: // external -typedef struct _plDrawList plDrawList; -typedef struct _plFontAtlas plFontAtlas; -typedef struct _plWindow plWindow; +typedef struct _plWindow plWindow; //----------------------------------------------------------------------------- // [SECTION] public api structs @@ -269,8 +266,8 @@ typedef struct _plDeviceI // memory plDynamicBinding (*allocate_dynamic_data)(plDevice*, size_t); - plDeviceAllocationBlock (*allocate_memory) (plDevice*, size_t, plMemoryMode, uint32_t uTypeFilter, const char* pcDebugName); - void (*free_memory) (plDevice*, plDeviceAllocationBlock*); + plDeviceMemoryAllocation(*allocate_memory) (plDevice*, size_t, plMemoryMode, uint32_t uTypeFilter, const char* pcDebugName); + void (*free_memory) (plDevice*, plDeviceMemoryAllocation*); // misc void (*flush_device)(plDevice* ptDevice); @@ -282,7 +279,6 @@ typedef struct _plGraphicsI void (*initialize)(plWindow*, const plGraphicsDesc*, plGraphics*); void (*resize) (plGraphics*); void (*cleanup) (plGraphics*); - void (*setup_ui) (plGraphics*, plRenderPassHandle); // per frame bool (*begin_frame)(plGraphics*); @@ -312,8 +308,8 @@ typedef struct _plGraphicsI // render encoder: direct (prefer draw stream, this should be used for bindless mostly) void (*bind_graphics_bind_groups)(plRenderEncoder*, plShaderHandle, uint32_t uFirst, uint32_t uCount, const plBindGroupHandle*, plDynamicBinding*); - void (*set_viewport) (plRenderEncoder*, plRenderViewport*); - void (*set_scissor_region) (plRenderEncoder*, plScissor*); + void (*set_viewport) (plRenderEncoder*, const plRenderViewport*); + void (*set_scissor_region) (plRenderEncoder*, const plScissor*); void (*bind_vertex_buffer) (plRenderEncoder*, plBufferHandle); void (*draw) (plRenderEncoder*, uint32_t uCount, const plDraw*); void (*draw_indexed) (plRenderEncoder*, uint32_t uCount, const plDrawIndex*); @@ -333,12 +329,6 @@ typedef struct _plGraphicsI void (*generate_mipmaps) (plBlitEncoder*, plTextureHandle); void (*copy_buffer) (plBlitEncoder*, plBufferHandle tSource, plBufferHandle tDestination, uint32_t uSourceOffset, uint32_t uDestinationOffset, size_t); void (*transfer_image_to_buffer)(plBlitEncoder*, plTextureHandle tTexture, plBufferHandle tBuffer); // from single layer & single mip textures - - // 2D drawing api (will be moved to an extension) - void (*draw_lists) (plGraphics*, plRenderEncoder, uint32_t uListCount, plDrawList*); - void (*create_font_atlas) (plFontAtlas*); - void (*destroy_font_atlas) (plFontAtlas*); - void* (*get_ui_texture_handle)(plGraphics*, plTextureHandle, plSamplerHandle); } plGraphicsI; //----------------------------------------------------------------------------- @@ -465,25 +455,15 @@ typedef struct _plDeviceMemoryRequirements typedef struct _plDeviceMemoryAllocation { plMemoryMode tMemoryMode; + uint64_t ulMemoryType; uint64_t uHandle; uint64_t ulOffset; uint64_t ulSize; char* pHostMapped; + uint32_t uCurrentIndex; // used but debug tool plDeviceMemoryAllocatorI* ptAllocator; } plDeviceMemoryAllocation; -typedef struct _plDeviceAllocationBlock -{ - plMemoryMode tMemoryMode; - uint64_t ulMemoryType; - uint64_t ulAddress; - uint64_t ulSize; - char* pHostMapped; - uint32_t uCurrentIndex; // used but debug tool - double dLastTimeUsed; - plDeviceMemoryAllocatorI *ptAllocator; -} plDeviceAllocationBlock; - typedef struct _plDeviceMemoryAllocatorI { struct plDeviceMemoryAllocatorO* ptInst; // opaque pointer @@ -537,6 +517,7 @@ typedef struct _plTexture plTextureViewDesc tView; plDeviceMemoryRequirements tMemoryRequirements; plDeviceMemoryAllocation tMemoryAllocation; + plBindGroupHandle _tDrawBindGroup; } plTexture; typedef struct _plBufferDescription @@ -853,15 +834,16 @@ typedef struct _plGraphicsDesc typedef struct _plGraphics { - plDevice tDevice; - plWindow* ptMainWindow; - plSwapchain tSwapchain; - uint32_t uCurrentFrameIndex; - uint32_t uFramesInFlight; - plFrameGarbage* sbtGarbage; - size_t szLocalMemoryInUse; - size_t szHostMemoryInUse; - bool bValidationActive; + plDevice tDevice; + plWindow* ptMainWindow; + plSwapchain tSwapchain; + uint32_t uCurrentFrameIndex; + uint32_t uFramesInFlight; + plFrameGarbage* sbtGarbage; + size_t szLocalMemoryInUse; + size_t szHostMemoryInUse; + bool bValidationActive; + plBindGroupHandle* sbtFreeDrawBindGroups; // render pass layouts plRenderPassLayout* sbtRenderPassLayoutsCold; diff --git a/extensions/pl_metal_ext.m b/extensions/pl_metal_ext.m index 03058867..82d1da58 100644 --- a/extensions/pl_metal_ext.m +++ b/extensions/pl_metal_ext.m @@ -25,10 +25,6 @@ #include "pl_memory.h" #include "pl_graphics_ext.c" -// pilotlight ui -#include "pl_ui.h" -#include "pl_ui_metal.h" - // metal stuff #import #import @@ -50,6 +46,7 @@ //----------------------------------------------------------------------------- const plFileI* gptFile = NULL; +const plIOI* gptIO = NULL; const plThreadsI* gptThread = NULL; //----------------------------------------------------------------------------- @@ -203,11 +200,10 @@ static bool pl__is_stencil_format (plFormat tFormat); static MTLBlendFactor pl__metal_blend_factor(plBlendFactor tFactor); static MTLBlendOperation pl__metal_blend_op(plBlendOp tOp); - static void pl__garbage_collect(plGraphics* ptGraphics); -static plDeviceAllocationBlock pl_allocate_memory(plDevice* ptDevice, size_t ulSize, plMemoryMode tMemoryMode, uint32_t uTypeFilter, const char* pcName); -static void pl_free_memory(plDevice* ptDevice, plDeviceAllocationBlock* ptBlock); +static plDeviceMemoryAllocation pl_allocate_memory(plDevice* ptDevice, size_t ulSize, plMemoryMode tMemoryMode, uint32_t uTypeFilter, const char* pcName); +static void pl_free_memory(plDevice* ptDevice, plDeviceMemoryAllocation* ptBlock); //----------------------------------------------------------------------------- // [SECTION] public api implementation @@ -220,13 +216,6 @@ return &ptMetalGraphics->sbFrames[ptGraphics->uCurrentFrameIndex]; } -static void* -pl_get_ui_texture_handle(plGraphics* ptGraphics, plTextureHandle tHandle, plSamplerHandle tSamplerHandle) -{ - plGraphicsMetal* ptMetalGraphics = ptGraphics->_pInternalData; - return ptMetalGraphics->sbtTexturesHot[tHandle.uIndex].tTexture; -} - static void pl_create_main_render_pass_layout(plDevice* ptDevice) { @@ -255,6 +244,13 @@ { .tFormat = ptGraphics->tSwapchain.tFormat, } + }, + .uSubpassCount = 1, + .atSubpasses = { + { + .uRenderTargetCount = 1, + .auRenderTargets = {0} + }, } } }; @@ -363,7 +359,8 @@ plRenderPass tRenderPass = { .tDesc = { - .tDimensions = {pl_get_io()->afMainViewportSize[0], pl_get_io()->afMainViewportSize[1]} + .tDimensions = {gptIO->get_io()->afMainViewportSize[0], gptIO->get_io()->afMainViewportSize[1]}, + .tLayout = ptGraphics->tMainRenderPassLayout }, .bSwapchain = true }; @@ -373,7 +370,6 @@ plMetalRenderPass* ptMetalRenderPass = &ptMetalGraphics->sbtRenderPassesHot[uResourceIndex]; // render pass descriptor - for(uint32_t i = 0; i < PL_FRAMES_IN_FLIGHT; i++) { MTLRenderPassDescriptor* ptRenderPassDescriptor = [MTLRenderPassDescriptor new]; @@ -381,6 +377,7 @@ ptRenderPassDescriptor.colorAttachments[0].loadAction = MTLLoadActionClear; ptRenderPassDescriptor.colorAttachments[0].storeAction = MTLStoreActionStore; ptRenderPassDescriptor.colorAttachments[0].clearColor = MTLClearColorMake(0.0, 0.0, 0.0, 1.0); + ptRenderPassDescriptor.colorAttachments[0].texture = ptMetalGraphics->tCurrentDrawable.texture; ptRenderPassDescriptor.depthAttachment.texture = nil; pl_sb_push(ptMetalRenderPass->atRenderPassDescriptors[i].sbptRenderPassDescriptor, ptRenderPassDescriptor); @@ -861,115 +858,6 @@ return tHandle; } -static void -pl_bind_texture_to_memory(plDevice* ptDevice, plTextureHandle tHandle, const plDeviceMemoryAllocation* ptAllocation) -{ - plGraphics* ptGraphics = ptDevice->ptGraphics; - plDeviceMetal* ptMetalDevice = (plDeviceMetal*)ptDevice->_pInternalData; - plGraphicsMetal* ptMetalGraphics = ptGraphics->_pInternalData; - - plTexture* ptTexture = &ptGraphics->sbtTexturesCold[tHandle.uIndex]; - ptTexture->tMemoryAllocation = *ptAllocation; - plMetalTexture* ptMetalTexture = &ptMetalGraphics->sbtTexturesHot[tHandle.uIndex]; - - MTLStorageMode tStorageMode = MTLStorageModeShared; - if(ptAllocation->tMemoryMode == PL_MEMORY_GPU) - { - tStorageMode = MTLStorageModePrivate; - } - ptMetalTexture->ptTextureDescriptor.storageMode = tStorageMode; - - ptMetalTexture->tTexture = [(id)ptAllocation->uHandle newTextureWithDescriptor:ptMetalTexture->ptTextureDescriptor offset:ptAllocation->ulOffset]; - ptMetalTexture->tHeap = (id)ptAllocation->uHandle; - ptMetalTexture->tTexture.label = [NSString stringWithUTF8String:ptTexture->tDesc.acDebugName]; - [ptMetalTexture->ptTextureDescriptor release]; - ptMetalTexture->ptTextureDescriptor = nil; -} - -static plTextureHandle -pl_create_texture_view(plDevice* ptDevice, const plTextureViewDesc* ptViewDesc, const char* pcName) -{ - plGraphics* ptGraphics = ptDevice->ptGraphics; - plDeviceMetal* ptMetalDevice = (plDeviceMetal*)ptDevice->_pInternalData; - plGraphicsMetal* ptMetalGraphics = ptGraphics->_pInternalData; - - uint32_t uTextureIndex = UINT32_MAX; - if(pl_sb_size(ptGraphics->sbtTextureFreeIndices) > 0) - uTextureIndex = pl_sb_pop(ptGraphics->sbtTextureFreeIndices); - else - { - uTextureIndex = pl_sb_size(ptGraphics->sbtTexturesCold); - pl_sb_add(ptGraphics->sbtTexturesCold); - pl_sb_push(ptGraphics->sbtTextureGenerations, UINT32_MAX); - pl_sb_add(ptMetalGraphics->sbtTexturesHot); - } - - plTextureHandle tHandle = { - .uGeneration = ++ptGraphics->sbtTextureGenerations[uTextureIndex], - .uIndex = uTextureIndex - }; - - plTexture tTexture = { - .tDesc = ptGraphics->sbtTexturesCold[ptViewDesc->tTexture.uIndex].tDesc, - .tView = *ptViewDesc - }; - - plTexture* ptTexture = pl__get_texture(ptDevice, ptViewDesc->tTexture); - plMetalTexture* ptOldMetalTexture = &ptMetalGraphics->sbtTexturesHot[ptViewDesc->tTexture.uIndex]; - plMetalTexture* ptNewMetalTexture = &ptMetalGraphics->sbtTexturesHot[uTextureIndex]; - ptNewMetalTexture->bOriginalView = false; - - // MTLTextureType tTextureType = MTLTextureType2D; - - // if(tTexture.tDesc.tType == PL_TEXTURE_TYPE_2D) - // tTextureType = MTLTextureType2D; - // else if(tTexture.tDesc.tType == PL_TEXTURE_TYPE_CUBE) - // tTextureType = MTLTextureTypeCube; - // else if(tTexture.tDesc.tType == PL_TEXTURE_TYPE_2D_ARRAY) - // tTextureType = MTLTextureType2DArray; - // // tTextureType = MTLTextureType2D; - // else - // { - // PL_ASSERT(false && "unsupported texture type"); - // } - - // NSRange tLevelRange = { - // .length = ptViewDesc->uMips == 0 ? ptTexture->tDesc.uMips - ptViewDesc->uBaseMip : ptViewDesc->uMips, - // .location = ptViewDesc->uBaseMip - // }; - - // NSRange tSliceRange = { - // .length = ptViewDesc->uLayerCount, - // .location = ptViewDesc->uBaseLayer - // }; - - // NSRange tLevelRange = { - // .length = ptTexture->tView.uMips, - // .location = ptTexture->tView.uBaseMip - // }; - - // NSRange tSliceRange = { - // .length = ptTexture->tView.uLayerCount, - // .location = ptTexture->tView.uBaseLayer - // }; - - // plMetalTexture tMetalTexture = { - // .tTexture = [ptOldMetalTexture->tTexture newTextureViewWithPixelFormat:pl__metal_format(ptViewDesc->tFormat) - // textureType:tTextureType - // levels:tLevelRange - // slices:tSliceRange], - // .tHeap = ptOldMetalTexture->tHeap - // }; - // if(pcName == NULL) - // pcName = "unnamed texture"; - // tMetalTexture.tTexture.label = [NSString stringWithUTF8String:pcName]; - ptNewMetalTexture->tTexture = ptOldMetalTexture->tTexture; - ptNewMetalTexture->tHeap = ptOldMetalTexture->tHeap; - - ptGraphics->sbtTexturesCold[uTextureIndex] = tTexture; - return tHandle; -} - static plSamplerHandle pl_create_sampler(plDevice* ptDevice, const plSamplerDesc* ptDesc, const char* pcName) { @@ -1225,6 +1113,177 @@ } } +static void +pl_bind_texture_to_memory(plDevice* ptDevice, plTextureHandle tHandle, const plDeviceMemoryAllocation* ptAllocation) +{ + plGraphics* ptGraphics = ptDevice->ptGraphics; + plDeviceMetal* ptMetalDevice = (plDeviceMetal*)ptDevice->_pInternalData; + plGraphicsMetal* ptMetalGraphics = ptGraphics->_pInternalData; + + plTexture* ptTexture = &ptGraphics->sbtTexturesCold[tHandle.uIndex]; + ptTexture->tMemoryAllocation = *ptAllocation; + plMetalTexture* ptMetalTexture = &ptMetalGraphics->sbtTexturesHot[tHandle.uIndex]; + + MTLStorageMode tStorageMode = MTLStorageModeShared; + if(ptAllocation->tMemoryMode == PL_MEMORY_GPU) + { + tStorageMode = MTLStorageModePrivate; + } + ptMetalTexture->ptTextureDescriptor.storageMode = tStorageMode; + + ptMetalTexture->tTexture = [(id)ptAllocation->uHandle newTextureWithDescriptor:ptMetalTexture->ptTextureDescriptor offset:ptAllocation->ulOffset]; + ptMetalTexture->tHeap = (id)ptAllocation->uHandle; + ptMetalTexture->tTexture.label = [NSString stringWithUTF8String:ptTexture->tDesc.acDebugName]; + [ptMetalTexture->ptTextureDescriptor release]; + ptMetalTexture->ptTextureDescriptor = nil; + + if(ptTexture->tDesc.tUsage & PL_TEXTURE_USAGE_SAMPLED) + { + if(pl_sb_size(ptGraphics->sbtFreeDrawBindGroups) == 0) + { + const plBindGroupLayout tDrawingBindGroup = { + .uTextureBindingCount = 1, + .atTextureBindings = { + {.uSlot = 0, .tStages = PL_STAGE_PIXEL, .tType = PL_TEXTURE_BINDING_TYPE_SAMPLED} + } + }; + ptGraphics->sbtTexturesCold[tHandle.uIndex]._tDrawBindGroup = pl_create_bind_group(ptDevice, &tDrawingBindGroup, "draw binding"); + } + else + { + ptGraphics->sbtTexturesCold[tHandle.uIndex]._tDrawBindGroup = pl_sb_pop(ptGraphics->sbtFreeDrawBindGroups); + } + + const plBindGroupUpdateTextureData atBGTextureData[] = { + { + .tTexture = tHandle, + .uSlot = 0, + .tType = PL_TEXTURE_BINDING_TYPE_SAMPLED + } + }; + const plBindGroupUpdateData tBGData = { + .uTextureCount = 1, + .atTextures = atBGTextureData + }; + pl_update_bind_group(&ptGraphics->tDevice, ptGraphics->sbtTexturesCold[tHandle.uIndex]._tDrawBindGroup, &tBGData); + } +} + +static plTextureHandle +pl_create_texture_view(plDevice* ptDevice, const plTextureViewDesc* ptViewDesc, const char* pcName) +{ + plGraphics* ptGraphics = ptDevice->ptGraphics; + plDeviceMetal* ptMetalDevice = (plDeviceMetal*)ptDevice->_pInternalData; + plGraphicsMetal* ptMetalGraphics = ptGraphics->_pInternalData; + + uint32_t uTextureIndex = UINT32_MAX; + if(pl_sb_size(ptGraphics->sbtTextureFreeIndices) > 0) + uTextureIndex = pl_sb_pop(ptGraphics->sbtTextureFreeIndices); + else + { + uTextureIndex = pl_sb_size(ptGraphics->sbtTexturesCold); + pl_sb_add(ptGraphics->sbtTexturesCold); + pl_sb_push(ptGraphics->sbtTextureGenerations, UINT32_MAX); + pl_sb_add(ptMetalGraphics->sbtTexturesHot); + } + + plTextureHandle tHandle = { + .uGeneration = ++ptGraphics->sbtTextureGenerations[uTextureIndex], + .uIndex = uTextureIndex + }; + + plTexture tTexture = { + .tDesc = ptGraphics->sbtTexturesCold[ptViewDesc->tTexture.uIndex].tDesc, + .tView = *ptViewDesc + }; + + plTexture* ptTexture = pl__get_texture(ptDevice, ptViewDesc->tTexture); + plMetalTexture* ptOldMetalTexture = &ptMetalGraphics->sbtTexturesHot[ptViewDesc->tTexture.uIndex]; + plMetalTexture* ptNewMetalTexture = &ptMetalGraphics->sbtTexturesHot[uTextureIndex]; + ptNewMetalTexture->bOriginalView = false; + + if(ptTexture->tDesc.tUsage & PL_TEXTURE_USAGE_SAMPLED) + { + if(pl_sb_size(ptGraphics->sbtFreeDrawBindGroups) == 0) + { + const plBindGroupLayout tDrawingBindGroup = { + .uTextureBindingCount = 1, + .atTextureBindings = { + {.uSlot = 0, .tStages = PL_STAGE_PIXEL, .tType = PL_TEXTURE_BINDING_TYPE_SAMPLED} + } + }; + ptGraphics->sbtTexturesCold[tHandle.uIndex]._tDrawBindGroup = pl_create_bind_group(ptDevice, &tDrawingBindGroup, "draw binding"); + } + else + { + ptGraphics->sbtTexturesCold[tHandle.uIndex]._tDrawBindGroup = pl_sb_pop(ptGraphics->sbtFreeDrawBindGroups); + } + + const plBindGroupUpdateTextureData atBGTextureData[] = { + { + .tTexture = tHandle, + .uSlot = 0, + .tType = PL_TEXTURE_BINDING_TYPE_SAMPLED + } + }; + const plBindGroupUpdateData tBGData = { + .uTextureCount = 1, + .atTextures = atBGTextureData + }; + pl_update_bind_group(&ptGraphics->tDevice, ptGraphics->sbtTexturesCold[tHandle.uIndex]._tDrawBindGroup, &tBGData); + } + + // MTLTextureType tTextureType = MTLTextureType2D; + + // if(tTexture.tDesc.tType == PL_TEXTURE_TYPE_2D) + // tTextureType = MTLTextureType2D; + // else if(tTexture.tDesc.tType == PL_TEXTURE_TYPE_CUBE) + // tTextureType = MTLTextureTypeCube; + // else if(tTexture.tDesc.tType == PL_TEXTURE_TYPE_2D_ARRAY) + // tTextureType = MTLTextureType2DArray; + // // tTextureType = MTLTextureType2D; + // else + // { + // PL_ASSERT(false && "unsupported texture type"); + // } + + // NSRange tLevelRange = { + // .length = ptViewDesc->uMips == 0 ? ptTexture->tDesc.uMips - ptViewDesc->uBaseMip : ptViewDesc->uMips, + // .location = ptViewDesc->uBaseMip + // }; + + // NSRange tSliceRange = { + // .length = ptViewDesc->uLayerCount, + // .location = ptViewDesc->uBaseLayer + // }; + + // NSRange tLevelRange = { + // .length = ptTexture->tView.uMips, + // .location = ptTexture->tView.uBaseMip + // }; + + // NSRange tSliceRange = { + // .length = ptTexture->tView.uLayerCount, + // .location = ptTexture->tView.uBaseLayer + // }; + + // plMetalTexture tMetalTexture = { + // .tTexture = [ptOldMetalTexture->tTexture newTextureViewWithPixelFormat:pl__metal_format(ptViewDesc->tFormat) + // textureType:tTextureType + // levels:tLevelRange + // slices:tSliceRange], + // .tHeap = ptOldMetalTexture->tHeap + // }; + // if(pcName == NULL) + // pcName = "unnamed texture"; + // tMetalTexture.tTexture.label = [NSString stringWithUTF8String:pcName]; + ptNewMetalTexture->tTexture = ptOldMetalTexture->tTexture; + ptNewMetalTexture->tHeap = ptOldMetalTexture->tHeap; + + ptGraphics->sbtTexturesCold[uTextureIndex] = tTexture; + return tHandle; +} + static plDynamicBinding pl_allocate_dynamic_data(plDevice* ptDevice, size_t szSize) { @@ -1583,8 +1642,8 @@ }; - plDeviceAllocationBlock tBlock = pl_allocate_memory(ptData->ptDevice, ulSize, PL_MEMORY_GPU_CPU, uTypeFilter, "Uncached Heap"); - tAllocation.uHandle = tBlock.ulAddress; + plDeviceMemoryAllocation tBlock = pl_allocate_memory(ptData->ptDevice, ulSize, PL_MEMORY_GPU_CPU, uTypeFilter, "Uncached Heap"); + tAllocation.uHandle = tBlock.uHandle; tAllocation.pHostMapped = tBlock.pHostMapped; ptData->ptDevice->ptGraphics->szHostMemoryInUse += ulSize; return tAllocation; @@ -1594,7 +1653,7 @@ pl_free_staging_dynamic(struct plDeviceMemoryAllocatorO* ptInst, plDeviceMemoryAllocation* ptAllocation) { plInternalDeviceAllocatorData* ptData = (plInternalDeviceAllocatorData*)ptInst; - plDeviceAllocationBlock tBlock = {.ulAddress = ptAllocation->uHandle}; + plDeviceMemoryAllocation tBlock = {.uHandle = ptAllocation->uHandle}; pl_free_memory(ptData->ptDevice, &tBlock); ptData->ptDevice->ptGraphics->szHostMemoryInUse -= ptAllocation->ulSize; ptAllocation->uHandle = 0; @@ -1606,7 +1665,7 @@ pl_initialize_graphics(plWindow* ptWindow, const plGraphicsDesc* ptDesc, plGraphics* ptGraphics) { ptGraphics->bValidationActive = ptDesc->bEnableValidation; - plIO* ptIOCtx = pl_get_io(); + plIO* ptIOCtx = gptIO->get_io(); ptGraphics->_pInternalData = PL_ALLOC(sizeof(plGraphicsMetal)); memset(ptGraphics->_pInternalData, 0, sizeof(plGraphicsMetal)); @@ -1686,18 +1745,10 @@ pl_create_main_render_pass(&ptGraphics->tDevice); } -static void -pl_setup_ui(plGraphics* ptGraphics, plRenderPassHandle tPass) -{ - plGraphicsMetal* ptMetalGraphics = (plGraphicsMetal*)ptGraphics->_pInternalData; - plDeviceMetal* ptMetalDevice = (plDeviceMetal*)ptGraphics->tDevice._pInternalData; - - pl_initialize_metal(ptMetalDevice->tDevice); -} - static void pl_resize(plGraphics* ptGraphics) { + ptGraphics->uCurrentFrameIndex = 0; } static bool @@ -1715,8 +1766,18 @@ dispatch_semaphore_wait(ptFrame->tFrameBoundarySemaphore, DISPATCH_TIME_FOREVER); - plIO* ptIOCtx = pl_get_io(); + plIO* ptIOCtx = gptIO->get_io(); ptMetalGraphics->pMetalLayer = ptIOCtx->pBackendPlatformData; + + static bool bFirstRun = true; + if(bFirstRun == false) + { + pl__garbage_collect(ptGraphics); + } + else + { + bFirstRun = false; + } // get next drawable ptMetalGraphics->tCurrentDrawable = [ptMetalGraphics->pMetalLayer nextDrawable]; @@ -1816,7 +1877,6 @@ [tCmdBuffer commit]; ptGraphics->uCurrentFrameIndex = (ptGraphics->uCurrentFrameIndex + 1) % ptGraphics->uFramesInFlight; - pl__garbage_collect(ptGraphics); return true; } @@ -1858,7 +1918,6 @@ ptMetalRenderPass->atRenderPassDescriptors[ptGraphics->uCurrentFrameIndex].sbptRenderPassDescriptor[0].colorAttachments[0].texture = ptMetalGraphics->tCurrentDrawable.texture; tRenderEncoder = [tCmdBuffer renderCommandEncoderWithDescriptor:ptMetalRenderPass->atRenderPassDescriptors[ptGraphics->uCurrentFrameIndex].sbptRenderPassDescriptor[0]]; tRenderEncoder.label = @"main encoder"; - pl_new_draw_frame_metal(ptMetalRenderPass->atRenderPassDescriptors[0].sbptRenderPassDescriptor[0]); } else { @@ -2093,7 +2152,7 @@ } static void -pl_set_viewport(plRenderEncoder* ptEncoder, plRenderViewport* ptViewport) +pl_set_viewport(plRenderEncoder* ptEncoder, const plRenderViewport* ptViewport) { plGraphics* ptGraphics = ptEncoder->ptGraphics; id tEncoder = (id)ptEncoder->_pInternal; @@ -2110,7 +2169,7 @@ } static void -pl_set_scissor_region(plRenderEncoder* ptEncoder, plScissor* ptScissor) +pl_set_scissor_region(plRenderEncoder* ptEncoder, const plScissor* ptScissor) { plGraphics* ptGraphics = ptEncoder->ptGraphics; id tEncoder = (id)ptEncoder->_pInternal; @@ -2438,8 +2497,6 @@ plDeviceMetal* ptMetalDevice = (plDeviceMetal*)ptGraphics->tDevice._pInternalData; - pl_cleanup_metal(); - for(uint32_t i = 0; i < pl_sb_size(ptMetalGraphics->sbFrames); i++) { plFrameContext* ptFrame = &ptMetalGraphics->sbFrames[i]; @@ -2469,23 +2526,6 @@ pl__cleanup_common_graphics(ptGraphics); } -static void -pl_draw_lists(plGraphics* ptGraphics, plRenderEncoder tEncoder, uint32_t uListCount, plDrawList* atLists) -{ - id tCmdBuffer = (id)tEncoder.tCommandBuffer._pInternal; - plGraphicsMetal* ptMetalGraphics = (plGraphicsMetal*)ptGraphics->_pInternalData; - plDeviceMetal* ptMetalDevice = (plDeviceMetal*)ptGraphics->tDevice._pInternalData; - id tRenderEncoder = (id)tEncoder._pInternal; - id tDevice = ptMetalDevice->tDevice; - plMetalRenderPass* ptMetalRenderPass = &ptMetalGraphics->sbtRenderPassesHot[tEncoder.tRenderPassHandle.uIndex]; - - plIO* ptIOCtx = pl_get_io(); - for(uint32_t i = 0; i < uListCount; i++) - { - pl_submit_metal_drawlist(&atLists[i], ptIOCtx->afMainViewportSize[0], ptIOCtx->afMainViewportSize[1], tRenderEncoder, tCmdBuffer, ptMetalRenderPass->atRenderPassDescriptors[0].sbptRenderPassDescriptor[tEncoder._uCurrentSubpass]); - } -} - //----------------------------------------------------------------------------- // [SECTION] internal api implementation //----------------------------------------------------------------------------- @@ -2739,7 +2779,7 @@ case PL_FORMAT_R32G32_FLOAT: return MTLVertexFormatFloat2; case PL_FORMAT_B8G8R8A8_UNORM: - case PL_FORMAT_R8G8B8A8_UNORM: return MTLVertexFormatUChar3; + case PL_FORMAT_R8G8B8A8_UNORM: return MTLVertexFormatUChar4; } PL_ASSERT(false && "Unsupported vertex format"); @@ -2880,7 +2920,14 @@ for(uint32_t i = 0; i < pl_sb_size(ptGarbage->sbtMemory); i++) { - ptGarbage->sbtMemory[i].ptAllocator->free(ptGarbage->sbtMemory[i].ptAllocator->ptInst, &ptGarbage->sbtMemory[i]); + if(ptGarbage->sbtMemory[i].ptAllocator) + { + ptGarbage->sbtMemory[i].ptAllocator->free(ptGarbage->sbtMemory[i].ptAllocator->ptInst, &ptGarbage->sbtMemory[i]); + } + else + { + pl_free_memory(&ptGraphics->tDevice, &ptGarbage->sbtMemory[i]); + } } pl_sb_reset(ptGarbage->sbtTextures); @@ -2898,7 +2945,7 @@ // [SECTION] device memory allocators //----------------------------------------------------------------------------- -static plDeviceAllocationBlock +static plDeviceMemoryAllocation pl_allocate_memory(plDevice* ptDevice, size_t szSize, plMemoryMode tMemoryMode, uint32_t uTypeFilter, const char* pcName) { plDeviceMetal* ptMetalDevice = ptDevice->_pInternalData; @@ -2908,9 +2955,10 @@ pcName = "unnamed memory block"; } - plDeviceAllocationBlock tBlock = { - .ulAddress = 0, - .ulSize = (uint64_t)szSize + plDeviceMemoryAllocation tBlock = { + .uHandle = 0, + .ulSize = (uint64_t)szSize, + .tMemoryMode = tMemoryMode }; MTLHeapDescriptor* ptHeapDescriptor = [MTLHeapDescriptor new]; @@ -2931,16 +2979,16 @@ id tNewHeap = [ptMetalDevice->tDevice newHeapWithDescriptor:ptHeapDescriptor]; tNewHeap.label = [NSString stringWithUTF8String:pcName]; - tBlock.ulAddress = (uint64_t)tNewHeap; + tBlock.uHandle = (uint64_t)tNewHeap; [ptHeapDescriptor release]; return tBlock; } static void -pl_free_memory(plDevice* ptDevice, plDeviceAllocationBlock* ptBlock) +pl_free_memory(plDevice* ptDevice, plDeviceMemoryAllocation* ptBlock) { - id tHeap = (id)ptBlock->ulAddress; + id tHeap = (id)ptBlock->uHandle; plDeviceMetal* ptMetalDevice = ptDevice->_pInternalData; @@ -2956,12 +3004,11 @@ { ptDevice->ptGraphics->szHostMemoryInUse -= ptBlock->ulSize; } - ptBlock->ulAddress = 0; + ptBlock->uHandle = 0; ptBlock->pHostMapped = NULL; ptBlock->ulSize = 0; ptBlock->tMemoryMode = 0; ptBlock->ulMemoryType = 0; - ptBlock->dLastTimeUsed = 0; } static void @@ -2978,7 +3025,10 @@ ptMetalGraphics->sbtBuffersHot[tHandle.uIndex].tBuffer = nil; plBuffer* ptBuffer = &ptGraphics->sbtBuffersCold[tHandle.uIndex]; - ptBuffer->tMemoryAllocation.ptAllocator->free(ptBuffer->tMemoryAllocation.ptAllocator->ptInst, &ptBuffer->tMemoryAllocation); + if(ptBuffer->tMemoryAllocation.ptAllocator) + ptBuffer->tMemoryAllocation.ptAllocator->free(ptBuffer->tMemoryAllocation.ptAllocator->ptInst, &ptBuffer->tMemoryAllocation); + else + pl_free_memory(ptDevice, &ptBuffer->tMemoryAllocation); } static void @@ -2996,7 +3046,10 @@ ptMetalTexture->tTexture = nil; plTexture* ptTexture = &ptGraphics->sbtTexturesCold[tHandle.uIndex]; - ptTexture->tMemoryAllocation.ptAllocator->free(ptTexture->tMemoryAllocation.ptAllocator->ptInst, &ptTexture->tMemoryAllocation); + if(ptTexture->tMemoryAllocation.ptAllocator) + ptTexture->tMemoryAllocation.ptAllocator->free(ptTexture->tMemoryAllocation.ptAllocator->ptInst, &ptTexture->tMemoryAllocation); + else + pl_free_memory(ptDevice, &ptTexture->tMemoryAllocation); } static void @@ -3091,7 +3144,6 @@ static const plGraphicsI tApi = { .initialize = pl_initialize_graphics, .resize = pl_resize, - .setup_ui = pl_setup_ui, .begin_frame = pl_begin_frame, .dispatch = pl_dispatch, .bind_compute_bind_groups = pl_bind_compute_bind_groups, @@ -3101,11 +3153,7 @@ .bind_vertex_buffer = pl_bind_vertex_buffer, .bind_shader = pl_bind_shader, .bind_compute_shader = pl_bind_compute_shader, - .draw_lists = pl_draw_lists, .cleanup = pl_cleanup, - .create_font_atlas = pl_create_metal_font_texture, - .destroy_font_atlas = pl_cleanup_metal_font_texture, - .get_ui_texture_handle = pl_get_ui_texture_handle, .begin_command_recording = pl_begin_command_recording, .end_command_recording = pl_end_command_recording, .submit_command_buffer = pl_submit_command_buffer, @@ -3185,10 +3233,10 @@ { const plDataRegistryI* ptDataRegistry = ptApiRegistry->first(PL_API_DATA_REGISTRY); pl_set_memory_context(ptDataRegistry->get_data(PL_CONTEXT_MEMORY)); - pl_set_context(ptDataRegistry->get_data("context")); pl_set_profile_context(ptDataRegistry->get_data("profile")); gptFile = ptApiRegistry->first(PL_API_FILE); gptThread = ptApiRegistry->first(PL_API_THREADS); + gptIO = ptApiRegistry->first(PL_API_IO); if(bReload) { ptApiRegistry->replace(ptApiRegistry->first(PL_API_GRAPHICS), pl_load_graphics_api()); @@ -3207,8 +3255,3 @@ } -//----------------------------------------------------------------------------- -// [SECTION] unity build -//----------------------------------------------------------------------------- - -#include "pl_ui_metal.m" diff --git a/extensions/pl_ref_renderer_ext.c b/extensions/pl_ref_renderer_ext.c index af29a5fb..f9238e62 100644 --- a/extensions/pl_ref_renderer_ext.c +++ b/extensions/pl_ref_renderer_ext.c @@ -29,7 +29,6 @@ Index of this file: #include "pl_memory.h" #define PL_MATH_INCLUDE_FUNCTIONS #include "pl_math.h" -#include "pl_ui.h" // extensions #include "pl_graphics_ext.h" @@ -39,7 +38,8 @@ Index of this file: #include "pl_stats_ext.h" #include "pl_gpu_allocators_ext.h" #include "pl_job_ext.h" -#include "pl_draw_3d_ext.h" +#include "pl_draw_ext.h" +#include "pl_ui_ext.h" #define PL_MAX_VIEWS_PER_SCENE 4 #define PL_MAX_LIGHTS 1000 @@ -204,7 +204,6 @@ typedef struct _plRefView // output texture plTextureHandle tFinalTexture[PL_FRAMES_IN_FLIGHT]; - plTextureId tFinalTextureID[PL_FRAMES_IN_FLIGHT]; // for showing in UI // lighting plBindGroupHandle tLightingBindGroup[PL_FRAMES_IN_FLIGHT]; @@ -380,7 +379,9 @@ static const plStatsI* gptStats = NULL; static const plGPUAllocatorsI* gptGpuAllocators = NULL; static const plThreadsI* gptThreads = NULL; static const plJobI* gptJob = NULL; -static const plDraw3dI* gptDraw3d = NULL; +static const plDrawI* gptDraw = NULL; +static const plUiI* gptUI = NULL; +static const plIOI* gptIO = NULL; //----------------------------------------------------------------------------- // [SECTION] internal API @@ -1001,9 +1002,6 @@ pl_refr_create_view(uint32_t uSceneHandle, plVec2 tDimensions) ptView->tAOMetalRoughnessTexture[i] = pl__refr_create_texture(&tAttachmentTextureDesc, "metalroughness original", i); ptView->tDepthTexture[i] = pl__refr_create_texture(&tDepthTextureDesc, "offscreen depth original", i); - // texture IDs - ptView->tFinalTextureID[i] = gptGfx->get_ui_texture_handle(ptGraphics, ptView->tFinalTexture[i], gptData->tDefaultSampler); - // buffers ptView->atGlobalBuffers[i] = pl__refr_create_staging_buffer(&atGlobalBuffersDesc, "global", i); @@ -1141,8 +1139,8 @@ pl_refr_create_view(uint32_t uSceneHandle, plVec2 tDimensions) ptView->tRenderPass = gptDevice->create_render_pass(&ptGraphics->tDevice, &tRenderPassDesc, atAttachmentSets); // register debug 3D drawlist - ptView->pt3DDrawList = gptDraw3d->request_drawlist(); - ptView->pt3DSelectionDrawList = gptDraw3d->request_drawlist(); + ptView->pt3DDrawList = gptDraw->request_3d_drawlist(); + ptView->pt3DSelectionDrawList = gptDraw->request_3d_drawlist(); // create lighting composition quad const uint32_t uVertexStartIndex = pl_sb_size(ptScene->sbtVertexPosBuffer); @@ -1274,9 +1272,6 @@ pl_refr_resize_view(uint32_t uSceneHandle, uint32_t uViewHandle, plVec2 tDimensi ptView->tAOMetalRoughnessTexture[i] = pl__refr_create_texture(&tAttachmentTextureDesc, "metalroughness", i); ptView->tDepthTexture[i] = pl__refr_create_texture(&tDepthTextureDesc, "offscreen depth", i); - // texture IDs - ptView->tFinalTextureID[i] = gptGfx->get_ui_texture_handle(ptGraphics, ptView->tFinalTexture[i], gptData->tDefaultSampler); - // lighting bind group plTempAllocator tTempAllocator = {0}; ptView->tLightingBindGroup[i] = gptDevice->create_bind_group(&ptGraphics->tDevice, &tLightingBindGroupLayout, pl_temp_allocator_sprintf(&tTempAllocator, "lighting bind group%u", i)); @@ -2762,7 +2757,7 @@ pl_refr_run_ecs(uint32_t uSceneHandle) { pl_begin_profile_sample(__FUNCTION__); plRefScene* ptScene = &gptData->sbtScenes[uSceneHandle]; - gptECS->run_animation_update_system(&ptScene->tComponentLibrary, pl_get_io()->fDeltaTime); + gptECS->run_animation_update_system(&ptScene->tComponentLibrary, gptIO->get_io()->fDeltaTime); gptECS->run_transform_update_system(&ptScene->tComponentLibrary); gptECS->run_hierarchy_update_system(&ptScene->tComponentLibrary); gptECS->run_inverse_kinematics_update_system(&ptScene->tComponentLibrary); @@ -3674,7 +3669,7 @@ pl_refr_render_scene(plCommandBuffer tCommandBuffer, uint32_t uSceneHandle, uint }; gptDevice->update_bind_group(&ptGraphics->tDevice, tOutlineGlobalBG, &tOutlineBGData0); - const plVec4 tOutlineColor = (plVec4){(float)sin(pl_get_io()->dTime * 3.0) * 0.25f + 0.75f, 0.0f, 0.0f, 1.0f}; + const plVec4 tOutlineColor = (plVec4){(float)sin(gptIO->get_io()->dTime * 3.0) * 0.25f + 0.75f, 0.0f, 0.0f, 1.0f}; const plVec4 tOutlineColor2 = (plVec4){0.0f, tOutlineColor.r, 0.0f, 1.0f}; for(uint32_t i = 0; i < uOutlineDrawableCount; i++) { @@ -3683,7 +3678,7 @@ pl_refr_render_scene(plCommandBuffer tCommandBuffer, uint32_t uSceneHandle, uint plTransformComponent* ptTransform = gptECS->get_component(&ptScene->tComponentLibrary, PL_COMPONENT_TYPE_TRANSFORM, ptObject->tTransform); plMeshComponent* ptMesh = gptECS->get_component(&ptScene->tComponentLibrary, PL_COMPONENT_TYPE_MESH, ptObject->tMesh); - gptDraw3d->add_aabb(ptView->pt3DSelectionDrawList, ptMesh->tAABBFinal.tMin, ptMesh->tAABBFinal.tMax, tOutlineColor2, 0.02f); + gptDraw->add_3d_aabb(ptView->pt3DSelectionDrawList, ptMesh->tAABBFinal.tMin, ptMesh->tAABBFinal.tMax, tOutlineColor2, 0.02f); plDynamicBinding tDynamicBinding = gptDevice->allocate_dynamic_data(ptDevice, sizeof(plOutlineDynamicData)); @@ -3719,7 +3714,7 @@ pl_refr_render_scene(plCommandBuffer tCommandBuffer, uint32_t uSceneHandle, uint if(ptScene->sbtLightData[i].iType == PL_LIGHT_TYPE_POINT) { const plVec4 tColor = {.rgb = ptScene->sbtLightData[i].tColor, .a = 1.0f}; - gptDraw3d->add_point(ptView->pt3DDrawList, ptScene->sbtLightData[i].tPosition, tColor, 0.25f, 0.02f); + gptDraw->add_3d_point(ptView->pt3DDrawList, ptScene->sbtLightData[i].tPosition, tColor, 0.25f, 0.02f); } } @@ -3730,13 +3725,13 @@ pl_refr_render_scene(plCommandBuffer tCommandBuffer, uint32_t uSceneHandle, uint { plMeshComponent* ptMesh = gptECS->get_component(&ptScene->tComponentLibrary, PL_COMPONENT_TYPE_MESH, ptScene->sbtOpaqueDrawables[i].tEntity); - gptDraw3d->add_aabb(ptView->pt3DDrawList, ptMesh->tAABBFinal.tMin, ptMesh->tAABBFinal.tMax, (plVec4){1.0f, 0.0f, 0.0f, 1.0f}, 0.02f); + gptDraw->add_3d_aabb(ptView->pt3DDrawList, ptMesh->tAABBFinal.tMin, ptMesh->tAABBFinal.tMax, (plVec4){1.0f, 0.0f, 0.0f, 1.0f}, 0.02f); } for(uint32_t i = 0; i < uTransparentDrawableCount; i++) { plMeshComponent* ptMesh = gptECS->get_component(&ptScene->tComponentLibrary, PL_COMPONENT_TYPE_MESH, ptScene->sbtTransparentDrawables[i].tEntity); - gptDraw3d->add_aabb(ptView->pt3DDrawList, ptMesh->tAABBFinal.tMin, ptMesh->tAABBFinal.tMax, (plVec4){1.0f, 0.0f, 0.0f, 1.0f}, 0.02f); + gptDraw->add_3d_aabb(ptView->pt3DDrawList, ptMesh->tAABBFinal.tMin, ptMesh->tAABBFinal.tMax, (plVec4){1.0f, 0.0f, 0.0f, 1.0f}, 0.02f); } } else if(tOptions.bShowVisibleBoundingBoxes) @@ -3745,31 +3740,31 @@ pl_refr_render_scene(plCommandBuffer tCommandBuffer, uint32_t uSceneHandle, uint { plMeshComponent* ptMesh = gptECS->get_component(&ptScene->tComponentLibrary, PL_COMPONENT_TYPE_MESH, ptView->sbtVisibleOpaqueDrawables[i].tEntity); - gptDraw3d->add_aabb(ptView->pt3DDrawList, ptMesh->tAABBFinal.tMin, ptMesh->tAABBFinal.tMax, (plVec4){1.0f, 0.0f, 0.0f, 1.0f}, 0.02f); + gptDraw->add_3d_aabb(ptView->pt3DDrawList, ptMesh->tAABBFinal.tMin, ptMesh->tAABBFinal.tMax, (plVec4){1.0f, 0.0f, 0.0f, 1.0f}, 0.02f); } for(uint32_t i = 0; i < uVisibleTransparentDrawCount; i++) { plMeshComponent* ptMesh = gptECS->get_component(&ptScene->tComponentLibrary, PL_COMPONENT_TYPE_MESH, ptView->sbtVisibleTransparentDrawables[i].tEntity); - gptDraw3d->add_aabb(ptView->pt3DDrawList, ptMesh->tAABBFinal.tMin, ptMesh->tAABBFinal.tMax, (plVec4){1.0f, 0.0f, 0.0f, 1.0f}, 0.02f); + gptDraw->add_3d_aabb(ptView->pt3DDrawList, ptMesh->tAABBFinal.tMin, ptMesh->tAABBFinal.tMax, (plVec4){1.0f, 0.0f, 0.0f, 1.0f}, 0.02f); } } if(tOptions.bShowOrigin) { const plMat4 tTransform = pl_identity_mat4(); - gptDraw3d->add_transform(ptView->pt3DDrawList, &tTransform, 10.0f, 0.02f); + gptDraw->add_3d_transform(ptView->pt3DDrawList, &tTransform, 10.0f, 0.02f); } if(tOptions.ptCullCamera && tOptions.ptCullCamera != tOptions.ptViewCamera) { - gptDraw3d->add_frustum(ptView->pt3DSelectionDrawList, &tOptions.ptCullCamera->tTransformMat, tOptions.ptCullCamera->fFieldOfView, tOptions.ptCullCamera->fAspectRatio, tOptions.ptCullCamera->fNearZ, tOptions.ptCullCamera->fFarZ, (plVec4){1.0f, 1.0f, 0.0f, 1.0f}, 0.02f); + gptDraw->add_3d_frustum(ptView->pt3DSelectionDrawList, &tOptions.ptCullCamera->tTransformMat, tOptions.ptCullCamera->fFieldOfView, tOptions.ptCullCamera->fAspectRatio, tOptions.ptCullCamera->fNearZ, tOptions.ptCullCamera->fFarZ, (plVec4){1.0f, 1.0f, 0.0f, 1.0f}, 0.02f); } const plMat4 tMVP = pl_mul_mat4(&ptCamera->tProjMat, &ptCamera->tViewMat); - gptDraw3d->submit_drawlist(ptView->pt3DDrawList, tEncoder, tDimensions.x, tDimensions.y, &tMVP, PL_3D_DRAW_FLAG_DEPTH_TEST | PL_3D_DRAW_FLAG_DEPTH_WRITE, 1); - gptDraw3d->submit_drawlist(ptView->pt3DSelectionDrawList, tEncoder, tDimensions.x, tDimensions.y, &tMVP, 0, 1); + gptDraw->submit_3d_drawlist(ptView->pt3DDrawList, tEncoder, tDimensions.x, tDimensions.y, &tMVP, PL_DRAW_FLAG_DEPTH_TEST | PL_DRAW_FLAG_DEPTH_WRITE, 1); + gptDraw->submit_3d_drawlist(ptView->pt3DSelectionDrawList, tEncoder, tDimensions.x, tDimensions.y, &tMVP, 0, 1); gptGfx->end_render_pass(&tEncoder); pl_end_profile_sample(); } @@ -3782,12 +3777,12 @@ pl_refr_get_debug_drawlist(uint32_t uSceneHandle, uint32_t uViewHandle) return ptView->pt3DDrawList; } -static plTextureId -pl_refr_get_view_texture_id(uint32_t uSceneHandle, uint32_t uViewHandle) +static plTextureHandle +pl_refr_get_view_color_texture(uint32_t uSceneHandle, uint32_t uViewHandle) { plRefScene* ptScene = &gptData->sbtScenes[uSceneHandle]; plRefView* ptView = &ptScene->atViews[uViewHandle]; - return ptView->tFinalTextureID[gptData->tGraphics.uCurrentFrameIndex]; + return ptView->tFinalTexture[gptData->tGraphics.uCurrentFrameIndex]; } static void @@ -4853,7 +4848,7 @@ pl_load_ref_renderer_api(void) .update_skin_textures = pl_refr_update_skin_textures, .perform_skinning = pl_refr_perform_skinning, .render_scene = pl_refr_render_scene, - .get_view_texture_id = pl_refr_get_view_texture_id, + .get_view_color_texture = pl_refr_get_view_color_texture, .resize_view = pl_refr_resize_view, .get_debug_drawlist = pl_refr_get_debug_drawlist, .generate_cascaded_shadow_map = pl_refr_generate_cascaded_shadow_map, @@ -4872,7 +4867,6 @@ pl_load_ext(plApiRegistryI* ptApiRegistry, bool bReload) pl_set_memory_context(gptDataRegistry->get_data(PL_CONTEXT_MEMORY)); pl_set_profile_context(gptDataRegistry->get_data("profile")); pl_set_log_context(gptDataRegistry->get_data("log")); - pl_set_context(gptDataRegistry->get_data("context")); // apis gptResource = ptApiRegistry->first(PL_API_RESOURCE); @@ -4886,7 +4880,9 @@ pl_load_ext(plApiRegistryI* ptApiRegistry, bool bReload) gptGpuAllocators = ptApiRegistry->first(PL_API_GPU_ALLOCATORS); gptThreads = ptApiRegistry->first(PL_API_THREADS); gptJob = ptApiRegistry->first(PL_API_JOB); - gptDraw3d = ptApiRegistry->first(PL_API_DRAW_3D); + gptDraw = ptApiRegistry->first(PL_API_DRAW); + gptUI = ptApiRegistry->first(PL_API_UI); + gptIO = ptApiRegistry->first(PL_API_IO); if(bReload) { diff --git a/extensions/pl_ref_renderer_ext.h b/extensions/pl_ref_renderer_ext.h index 392ec86f..ca3a3807 100644 --- a/extensions/pl_ref_renderer_ext.h +++ b/extensions/pl_ref_renderer_ext.h @@ -41,6 +41,7 @@ typedef struct _plWindow plWindow; // pl_os.h typedef struct _plGraphics plGraphics; // pl_graphics_ext.h typedef struct _plDrawList3D plDrawList3D; // pl_graphics_ext.h typedef struct _plCommandBuffer plCommandBuffer; // pl_graphics_ext.h +typedef union plTextureHandle plTextureHandle; // pl_graphics_ext.h typedef struct _plComponentLibrary plComponentLibrary; // pl_ecs_ext.h typedef struct _plCameraComponent plCameraComponent; // pl_ecs_ext.h typedef union _plEntity plEntity; // pl_ecs_ext.h @@ -65,7 +66,7 @@ typedef struct _plRefRendererI // views uint32_t (*create_view)(uint32_t uSceneHandle, plVec2 tDimensions); - plTextureId (*get_view_texture_id)(uint32_t uSceneHandle, uint32_t uViewHandle); + plTextureHandle (*get_view_color_texture)(uint32_t uSceneHandle, uint32_t uViewHandle); void (*resize_view)(uint32_t uSceneHandle, uint32_t uViewHandle, plVec2 tDimensions); // loading diff --git a/extensions/pl_vulkan_ext.c b/extensions/pl_vulkan_ext.c index 8d3e4e6d..a9d4439f 100644 --- a/extensions/pl_vulkan_ext.c +++ b/extensions/pl_vulkan_ext.c @@ -13,7 +13,6 @@ Index of this file: // [SECTION] internal api implementation // [SECTION] device memory allocators // [SECTION] extension loading -// [SECTION] unity build */ //----------------------------------------------------------------------------- @@ -43,8 +42,6 @@ Index of this file: #define PL_DEVICE_LOCAL_LEVELS 8 #endif -#include "pl_ui.h" -#include "pl_ui_vulkan.h" #include "vulkan/vulkan.h" #ifdef _WIN32 @@ -60,7 +57,8 @@ Index of this file: // [SECTION] global data //----------------------------------------------------------------------------- -const plFileI* gptFile = NULL; +const plFileI* gptFile = NULL; +const plIOI* gptIO = NULL; static uint32_t uLogChannel = UINT32_MAX; //----------------------------------------------------------------------------- @@ -244,8 +242,8 @@ static VkStencilOp pl__vulkan_stencil_op(plStencilOp tSt static VkBlendFactor pl__vulkan_blend_factor(plBlendFactor tFactor); static VkBlendOp pl__vulkan_blend_op(plBlendOp tOp); -static plDeviceAllocationBlock pl_allocate_memory(plDevice* ptDevice, size_t ulSize, plMemoryMode tMemoryMode, uint32_t uTypeFilter, const char* pcName); -static void pl_free_memory(plDevice* ptDevice, plDeviceAllocationBlock* ptBlock); +static plDeviceMemoryAllocation pl_allocate_memory(plDevice*, size_t ulSize, plMemoryMode tMemoryMode, uint32_t uTypeFilter, const char* pcName); +static void pl_free_memory(plDevice*, plDeviceMemoryAllocation*); static void pl_set_vulkan_object_name(plDevice* ptDevice, uint64_t uObjectHandle, VkDebugReportObjectTypeEXT tObjectType, const char* pcName); static plFrameContext* pl__get_frame_resources(plGraphics* ptGraphics); @@ -267,13 +265,6 @@ static VKAPI_ATTR VkBool32 VKAPI_CALL pl__debug_callback(VkDebugUtilsMessageSeve // [SECTION] public api implementation //----------------------------------------------------------------------------- -static void* -pl_get_ui_texture_handle(plGraphics* ptGraphics, plTextureHandle tHandle, plSamplerHandle tSamplerHandle) -{ - plVulkanGraphics* ptVulkanGfx = ptGraphics->_pInternalData; - return pl_add_texture(ptVulkanGfx->sbtTexturesHot[tHandle.uIndex].tImageView, ptVulkanGfx->sbtSamplersHot[tSamplerHandle.uIndex], VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); -} - static plSemaphoreHandle pl_create_semaphore(plDevice* ptDevice, bool bHostVisible) { @@ -664,193 +655,6 @@ pl_generate_mipmaps(plBlitEncoder* ptEncoder, plTextureHandle tTexture) } } -static plTextureHandle -pl_create_texture(plDevice* ptDevice, const plTextureDesc* ptDesc, const char* pcName) -{ - plGraphics* ptGraphics = ptDevice->ptGraphics; - plVulkanDevice* ptVulkanDevice = ptDevice->_pInternalData; - plVulkanGraphics* ptVulkanGraphics = ptGraphics->_pInternalData; - - if(pcName == NULL) - pcName = "unnamed texture"; - - plTextureDesc tDesc = *ptDesc; - strncpy(tDesc.acDebugName, pcName, PL_MAX_NAME_LENGTH); - - if(tDesc.tInitialUsage == PL_TEXTURE_USAGE_UNSPECIFIED) - tDesc.tInitialUsage = PL_TEXTURE_USAGE_SAMPLED; - - if(tDesc.uMips == 0) - tDesc.uMips = (uint32_t)floorf(log2f((float)pl_maxi((int)tDesc.tDimensions.x, (int)tDesc.tDimensions.y))) + 1u; - - uint32_t uTextureIndex = UINT32_MAX; - if(pl_sb_size(ptGraphics->sbtTextureFreeIndices) > 0) - uTextureIndex = pl_sb_pop(ptGraphics->sbtTextureFreeIndices); - else - { - uTextureIndex = pl_sb_size(ptGraphics->sbtTexturesCold); - pl_sb_add(ptGraphics->sbtTexturesCold); - pl_sb_push(ptGraphics->sbtTextureGenerations, UINT32_MAX); - pl_sb_add(ptVulkanGraphics->sbtTexturesHot); - } - - plTextureHandle tHandle = { - .uGeneration = ++ptGraphics->sbtTextureGenerations[uTextureIndex], - .uIndex = uTextureIndex - }; - - plTexture tTexture = { - .tDesc = tDesc, - .tView = { - .tFormat = tDesc.tFormat, - .uBaseMip = 0, - .uMips = tDesc.uMips, - .uBaseLayer = 0, - .uLayerCount = tDesc.uLayers, - .tTexture = tHandle - } - }; - - plVulkanTexture tVulkanTexture = { - .bOriginalView = true - }; - - VkImageViewType tImageViewType = 0; - if(tDesc.tType == PL_TEXTURE_TYPE_CUBE) - tImageViewType = VK_IMAGE_VIEW_TYPE_CUBE; - else if(tDesc.tType == PL_TEXTURE_TYPE_2D) - tImageViewType = VK_IMAGE_VIEW_TYPE_2D; - else if(tDesc.tType == PL_TEXTURE_TYPE_2D_ARRAY) - tImageViewType = VK_IMAGE_VIEW_TYPE_2D_ARRAY; - else - { - PL_ASSERT(false && "unsupported texture type"); - } - - VkImageUsageFlags tUsageFlags = 0; - if(tDesc.tUsage & PL_TEXTURE_USAGE_SAMPLED) tUsageFlags |= VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT; - if(tDesc.tUsage & PL_TEXTURE_USAGE_COLOR_ATTACHMENT) tUsageFlags |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT; - if(tDesc.tUsage & PL_TEXTURE_USAGE_DEPTH_STENCIL_ATTACHMENT) tUsageFlags |= VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT; - if(tDesc.tUsage & PL_TEXTURE_USAGE_TRANSIENT_ATTACHMENT) tUsageFlags |= VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT; - if(tDesc.tUsage & PL_TEXTURE_USAGE_INPUT_ATTACHMENT) tUsageFlags |= VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT; - - // create vulkan image - VkImageCreateInfo tImageInfo = { - .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, - .imageType = VK_IMAGE_TYPE_2D, - .extent.width = (uint32_t)tDesc.tDimensions.x, - .extent.height = (uint32_t)tDesc.tDimensions.y, - .extent.depth = (uint32_t)tDesc.tDimensions.z, - .mipLevels = tDesc.uMips, - .arrayLayers = tDesc.uLayers, - .format = pl__vulkan_format(tDesc.tFormat), - .tiling = VK_IMAGE_TILING_OPTIMAL, - .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, - .usage = tUsageFlags, - .sharingMode = VK_SHARING_MODE_EXCLUSIVE, - .samples = 1, - .flags = tImageViewType == VK_IMAGE_VIEW_TYPE_CUBE ? VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT : 0 - }; - - PL_VULKAN(vkCreateImage(ptVulkanDevice->tLogicalDevice, &tImageInfo, NULL, &tVulkanTexture.tImage)); - - // get memory requirements - VkMemoryRequirements tMemoryRequirements = {0}; - vkGetImageMemoryRequirements(ptVulkanDevice->tLogicalDevice, tVulkanTexture.tImage, &tMemoryRequirements); - tTexture.tMemoryRequirements.ulSize = tMemoryRequirements.size; - tTexture.tMemoryRequirements.ulAlignment = tMemoryRequirements.alignment; - tTexture.tMemoryRequirements.uMemoryTypeBits = tMemoryRequirements.memoryTypeBits; - - // upload data - ptVulkanGraphics->sbtTexturesHot[uTextureIndex] = tVulkanTexture; - ptGraphics->sbtTexturesCold[uTextureIndex] = tTexture; - return tHandle; -} - -static void -pl_bind_texture_to_memory(plDevice* ptDevice, plTextureHandle tHandle, const plDeviceMemoryAllocation* ptAllocation) -{ - plVulkanDevice* ptVulkanDevice = ptDevice->_pInternalData; - plVulkanGraphics* ptVulkanGraphics = ptDevice->ptGraphics->_pInternalData; - plGraphics* ptGraphics = ptDevice->ptGraphics; - - plTexture* ptTexture = &ptGraphics->sbtTexturesCold[tHandle.uIndex]; - ptTexture->tMemoryAllocation = *ptAllocation; - plVulkanTexture* ptVulkanTexture = &ptVulkanGraphics->sbtTexturesHot[tHandle.uIndex]; - - PL_VULKAN(vkBindImageMemory(ptVulkanDevice->tLogicalDevice, ptVulkanTexture->tImage, (VkDeviceMemory)ptAllocation->uHandle, ptAllocation->ulOffset)); - - VkImageAspectFlags tImageAspectFlags = ptTexture->tDesc.tUsage & PL_TEXTURE_USAGE_DEPTH_STENCIL_ATTACHMENT ? VK_IMAGE_ASPECT_DEPTH_BIT : VK_IMAGE_ASPECT_COLOR_BIT; - - if(pl__format_has_stencil(pl__vulkan_format(ptTexture->tDesc.tFormat))) - tImageAspectFlags |= VK_IMAGE_ASPECT_STENCIL_BIT; - - VkCommandBuffer tCommandBuffer = {0}; - - const VkCommandBufferAllocateInfo tAllocInfo = { - .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, - .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY, - .commandPool = ptVulkanDevice->tCmdPool, - .commandBufferCount = 1u, - }; - vkAllocateCommandBuffers(ptVulkanDevice->tLogicalDevice, &tAllocInfo, &tCommandBuffer); - - const VkCommandBufferBeginInfo tBeginInfo = { - .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, - .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT - }; - - vkBeginCommandBuffer(tCommandBuffer, &tBeginInfo); - - VkImageSubresourceRange tRange = { - .baseMipLevel = 0, - .levelCount = ptTexture->tDesc.uMips, - .baseArrayLayer = 0, - .layerCount = ptTexture->tDesc.uLayers, - .aspectMask = tImageAspectFlags - }; - - pl__transition_image_layout(tCommandBuffer, ptVulkanTexture->tImage, VK_IMAGE_LAYOUT_UNDEFINED, pl__vulkan_layout(ptTexture->tDesc.tInitialUsage), tRange, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT); - - PL_VULKAN(vkEndCommandBuffer(tCommandBuffer)); - const VkSubmitInfo tSubmitInfo = { - .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, - .commandBufferCount = 1u, - .pCommandBuffers = &tCommandBuffer, - }; - - PL_VULKAN(vkQueueSubmit(ptVulkanDevice->tGraphicsQueue, 1, &tSubmitInfo, VK_NULL_HANDLE)); - PL_VULKAN(vkDeviceWaitIdle(ptVulkanDevice->tLogicalDevice)); - vkFreeCommandBuffers(ptVulkanDevice->tLogicalDevice, ptVulkanDevice->tCmdPool, 1, &tCommandBuffer); - - //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~create view~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - VkImageViewType tImageViewType = 0; - if(ptTexture->tDesc.tType == PL_TEXTURE_TYPE_CUBE) - tImageViewType = VK_IMAGE_VIEW_TYPE_CUBE; - else if(ptTexture->tDesc.tType == PL_TEXTURE_TYPE_2D) - tImageViewType = VK_IMAGE_VIEW_TYPE_2D; - else if(ptTexture->tDesc.tType == PL_TEXTURE_TYPE_2D_ARRAY) - tImageViewType = VK_IMAGE_VIEW_TYPE_2D_ARRAY; - else - { - PL_ASSERT(false && "unsupported texture type"); - } - - VkImageViewCreateInfo tViewInfo = { - .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, - .image = ptVulkanTexture->tImage, - .viewType = tImageViewType, - .format = pl__vulkan_format(ptTexture->tDesc.tFormat), - .subresourceRange.baseMipLevel = ptTexture->tView.uBaseMip, - .subresourceRange.levelCount = ptTexture->tDesc.uMips, - .subresourceRange.baseArrayLayer = ptTexture->tView.uBaseLayer, - .subresourceRange.layerCount = ptTexture->tView.uLayerCount, - .subresourceRange.aspectMask = ptTexture->tDesc.tUsage & PL_TEXTURE_USAGE_DEPTH_STENCIL_ATTACHMENT ? VK_IMAGE_ASPECT_DEPTH_BIT : VK_IMAGE_ASPECT_COLOR_BIT, - }; - PL_VULKAN(vkCreateImageView(ptVulkanDevice->tLogicalDevice, &tViewInfo, NULL, &ptVulkanTexture->tImageView)); -} - static plTextureHandle pl_create_swapchain_texture_view(plDevice* ptDevice, const plTextureViewDesc* ptViewDesc, VkImage tImage, const char* pcName) { @@ -900,73 +704,6 @@ pl_create_swapchain_texture_view(plDevice* ptDevice, const plTextureViewDesc* pt return tHandle; } -static plTextureHandle -pl_create_texture_view(plDevice* ptDevice, const plTextureViewDesc* ptViewDesc, const char* pcName) -{ - plGraphics* ptGraphics = ptDevice->ptGraphics; - plVulkanDevice* ptVulkanDevice = ptDevice->_pInternalData; - plVulkanGraphics* ptVulkanGraphics = ptGraphics->_pInternalData; - - uint32_t uTextureIndex = UINT32_MAX; - if(pl_sb_size(ptGraphics->sbtTextureFreeIndices) > 0) - uTextureIndex = pl_sb_pop(ptGraphics->sbtTextureFreeIndices); - else - { - uTextureIndex = pl_sb_size(ptGraphics->sbtTexturesCold); - pl_sb_add(ptGraphics->sbtTexturesCold); - pl_sb_push(ptGraphics->sbtTextureGenerations, UINT32_MAX); - pl_sb_add(ptVulkanGraphics->sbtTexturesHot); - } - - plTextureHandle tHandle = { - .uGeneration = ++ptGraphics->sbtTextureGenerations[uTextureIndex], - .uIndex = uTextureIndex - }; - - plTexture tTexture = { - .tDesc = ptGraphics->sbtTexturesCold[ptViewDesc->tTexture.uIndex].tDesc, - .tView = *ptViewDesc - }; - - plTexture* ptTexture = pl__get_texture(ptDevice, ptViewDesc->tTexture); - plVulkanTexture* ptOldVulkanTexture = &ptVulkanGraphics->sbtTexturesHot[ptViewDesc->tTexture.uIndex]; - plVulkanTexture* ptNewVulkanTexture = &ptVulkanGraphics->sbtTexturesHot[tHandle.uIndex]; - - //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~create view~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - VkImageViewType tImageViewType = 0; - if(ptTexture->tDesc.tType == PL_TEXTURE_TYPE_CUBE) - tImageViewType = VK_IMAGE_VIEW_TYPE_CUBE; - else if(ptTexture->tDesc.tType == PL_TEXTURE_TYPE_2D) - tImageViewType = VK_IMAGE_VIEW_TYPE_2D; - else if(ptTexture->tDesc.tType == PL_TEXTURE_TYPE_2D_ARRAY) - tImageViewType = VK_IMAGE_VIEW_TYPE_2D; - // tImageViewType = VK_IMAGE_VIEW_TYPE_2D_ARRAY; - else - { - PL_ASSERT(false && "unsupported texture type"); - } - - VkImageAspectFlags tImageAspectFlags = ptTexture->tDesc.tUsage & PL_TEXTURE_USAGE_DEPTH_STENCIL_ATTACHMENT ? VK_IMAGE_ASPECT_DEPTH_BIT : VK_IMAGE_ASPECT_COLOR_BIT; - - VkImageViewCreateInfo tViewInfo = { - .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, - .image = ptOldVulkanTexture->tImage, - .viewType = tImageViewType, - .format = pl__vulkan_format(ptViewDesc->tFormat), - .subresourceRange.baseMipLevel = ptViewDesc->uBaseMip, - .subresourceRange.levelCount = ptViewDesc->uMips == 0 ? ptTexture->tDesc.uMips - ptViewDesc->uBaseMip : ptViewDesc->uMips, - .subresourceRange.baseArrayLayer = ptViewDesc->uBaseLayer, - .subresourceRange.layerCount = ptViewDesc->uLayerCount, - .subresourceRange.aspectMask = tImageAspectFlags, - }; - PL_VULKAN(vkCreateImageView(ptVulkanDevice->tLogicalDevice, &tViewInfo, NULL, &ptNewVulkanTexture->tImageView)); - - ptNewVulkanTexture->bOriginalView = false; - ptGraphics->sbtTexturesCold[uTextureIndex] = tTexture; - return tHandle; -} - static plSamplerHandle pl_create_sampler(plDevice* ptDevice, const plSamplerDesc* ptDesc, const char* pcName) { @@ -1346,116 +1083,433 @@ pl_get_temporary_bind_group(plDevice* ptDevice, const plBindGroupLayout* ptLayou VkDescriptorSetLayout tDescriptorSetLayout = VK_NULL_HANDLE; PL_VULKAN(vkCreateDescriptorSetLayout(ptVulkanDevice->tLogicalDevice, &tDescriptorSetLayoutInfo, NULL, &tDescriptorSetLayout)); - pl_temp_allocator_reset(&ptVulkanGraphics->tTempAllocator); + pl_temp_allocator_reset(&ptVulkanGraphics->tTempAllocator); + + plVulkanBindGroup tVulkanBindGroup = { + .tDescriptorSetLayout = tDescriptorSetLayout + }; + + VkDescriptorSetVariableDescriptorCountAllocateInfoEXT variableDescriptorCountAllocInfo = { + .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_VARIABLE_DESCRIPTOR_COUNT_ALLOCATE_INFO_EXT, + .descriptorSetCount = 1, + .pDescriptorCounts = &tDescriptorCount, + }; + + // allocate descriptor sets + const VkDescriptorSetAllocateInfo tAllocInfo = { + .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO, + .descriptorPool = ptCurrentFrame->tDynamicDescriptorPool, + .descriptorSetCount = 1, + .pSetLayouts = &tDescriptorSetLayout, + .pNext = bHasVariableDescriptors ? &variableDescriptorCountAllocInfo : NULL + }; + + PL_VULKAN(vkAllocateDescriptorSets(ptVulkanDevice->tLogicalDevice, &tAllocInfo, &tVulkanBindGroup.tDescriptorSet)); + + ptVulkanGraphics->sbtBindGroupsHot[uBindGroupIndex] = tVulkanBindGroup; + ptGraphics->sbtBindGroupsCold[uBindGroupIndex] = tBindGroup; + pl_queue_bind_group_for_deletion(ptDevice, tHandle); + return tHandle; +} + +static void +pl_update_bind_group(plDevice* ptDevice, plBindGroupHandle tHandle, const plBindGroupUpdateData* ptData) +{ + plGraphics* ptGraphics = ptDevice->ptGraphics; + plVulkanDevice* ptVulkanDevice = ptDevice->_pInternalData; + plVulkanGraphics* ptVulkanGraphics = ptGraphics->_pInternalData; + + plBindGroup* ptBindGroup = &ptGraphics->sbtBindGroupsCold[tHandle.uIndex]; + plVulkanBindGroup* ptVulkanBindGroup = &ptVulkanGraphics->sbtBindGroupsHot[tHandle.uIndex]; + + VkWriteDescriptorSet* sbtWrites = pl_temp_allocator_alloc(&ptVulkanGraphics->tTempAllocator, (ptData->uBufferCount + ptData->uSamplerCount + ptData->uTextureCount) * sizeof(VkWriteDescriptorSet)); + + VkDescriptorBufferInfo* sbtBufferDescInfos = ptData->uBufferCount > 0 ? pl_temp_allocator_alloc(&ptVulkanGraphics->tTempAllocator, ptData->uBufferCount * sizeof(VkDescriptorBufferInfo)) : NULL; + VkDescriptorImageInfo* sbtImageDescInfos = ptData->uTextureCount > 0 ? pl_temp_allocator_alloc(&ptVulkanGraphics->tTempAllocator, ptData->uTextureCount * sizeof(VkDescriptorImageInfo)) : NULL; + VkDescriptorImageInfo* sbtSamplerDescInfos = ptData->uSamplerCount > 0 ? pl_temp_allocator_alloc(&ptVulkanGraphics->tTempAllocator, ptData->uSamplerCount * sizeof(VkDescriptorImageInfo)) : NULL; + + static const VkDescriptorType atDescriptorTypeLUT[] = + { + VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, + VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, + }; + + uint32_t uCurrentWrite = 0; + for(uint32_t i = 0 ; i < ptData->uBufferCount; i++) + { + + const plVulkanBuffer* ptVulkanBuffer = &ptVulkanGraphics->sbtBuffersHot[ptData->atBuffers[i].tBuffer.uIndex]; + + sbtBufferDescInfos[i].buffer = ptVulkanBuffer->tBuffer; + sbtBufferDescInfos[i].offset = ptData->atBuffers[i].szOffset; + sbtBufferDescInfos[i].range = ptData->atBuffers[i].szBufferRange; + + sbtWrites[uCurrentWrite].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + sbtWrites[uCurrentWrite].dstBinding = ptData->atBuffers[i].uSlot; + sbtWrites[uCurrentWrite].dstArrayElement = 0; + sbtWrites[uCurrentWrite].descriptorType = atDescriptorTypeLUT[ptBindGroup->tLayout.aBufferBindings[i].tType - 1]; + sbtWrites[uCurrentWrite].descriptorCount = 1; + sbtWrites[uCurrentWrite].dstSet = ptVulkanBindGroup->tDescriptorSet; + sbtWrites[uCurrentWrite].pBufferInfo = &sbtBufferDescInfos[i]; + sbtWrites[uCurrentWrite].pNext = NULL; + uCurrentWrite++; + } + + static const VkDescriptorType atTextureDescriptorTypeLUT[] = + { + VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, + VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT, + }; + for(uint32_t i = 0 ; i < ptData->uTextureCount; i++) + { + + sbtImageDescInfos[i].imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + sbtImageDescInfos[i].imageView = ptVulkanGraphics->sbtTexturesHot[ptData->atTextures[i].tTexture.uIndex].tImageView; + sbtWrites[uCurrentWrite].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + sbtWrites[uCurrentWrite].dstBinding = ptData->atTextures[i].uSlot; + sbtWrites[uCurrentWrite].dstArrayElement = ptData->atTextures[i].uIndex; + sbtWrites[uCurrentWrite].descriptorType = atTextureDescriptorTypeLUT[ptData->atTextures[i].tType - 1]; + sbtWrites[uCurrentWrite].descriptorCount = 1; + sbtWrites[uCurrentWrite].dstSet = ptVulkanBindGroup->tDescriptorSet; + sbtWrites[uCurrentWrite].pImageInfo = &sbtImageDescInfos[i]; + sbtWrites[uCurrentWrite].pNext = NULL; + uCurrentWrite++; + } + + for(uint32_t i = 0 ; i < ptData->uSamplerCount; i++) + { + + sbtSamplerDescInfos[i].sampler = ptVulkanGraphics->sbtSamplersHot[ptData->atSamplerBindings[i].tSampler.uIndex]; + sbtWrites[uCurrentWrite].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + sbtWrites[uCurrentWrite].dstBinding = ptData->atSamplerBindings[i].uSlot; + sbtWrites[uCurrentWrite].dstArrayElement = 0; + sbtWrites[uCurrentWrite].descriptorType = VK_DESCRIPTOR_TYPE_SAMPLER; + sbtWrites[uCurrentWrite].descriptorCount = 1; + sbtWrites[uCurrentWrite].dstSet = ptVulkanBindGroup->tDescriptorSet; + sbtWrites[uCurrentWrite].pImageInfo = &sbtSamplerDescInfos[i]; + sbtWrites[uCurrentWrite].pNext = NULL; + uCurrentWrite++; + } + + vkUpdateDescriptorSets(ptVulkanDevice->tLogicalDevice, uCurrentWrite, sbtWrites, 0, NULL); + pl_temp_allocator_reset(&ptVulkanGraphics->tTempAllocator); +} + +static plTextureHandle +pl_create_texture(plDevice* ptDevice, const plTextureDesc* ptDesc, const char* pcName) +{ + plGraphics* ptGraphics = ptDevice->ptGraphics; + plVulkanDevice* ptVulkanDevice = ptDevice->_pInternalData; + plVulkanGraphics* ptVulkanGraphics = ptGraphics->_pInternalData; + + if(pcName == NULL) + pcName = "unnamed texture"; + + plTextureDesc tDesc = *ptDesc; + strncpy(tDesc.acDebugName, pcName, PL_MAX_NAME_LENGTH); + + if(tDesc.tInitialUsage == PL_TEXTURE_USAGE_UNSPECIFIED) + tDesc.tInitialUsage = PL_TEXTURE_USAGE_SAMPLED; + + if(tDesc.uMips == 0) + tDesc.uMips = (uint32_t)floorf(log2f((float)pl_maxi((int)tDesc.tDimensions.x, (int)tDesc.tDimensions.y))) + 1u; + + uint32_t uTextureIndex = UINT32_MAX; + if(pl_sb_size(ptGraphics->sbtTextureFreeIndices) > 0) + uTextureIndex = pl_sb_pop(ptGraphics->sbtTextureFreeIndices); + else + { + uTextureIndex = pl_sb_size(ptGraphics->sbtTexturesCold); + pl_sb_add(ptGraphics->sbtTexturesCold); + pl_sb_push(ptGraphics->sbtTextureGenerations, UINT32_MAX); + pl_sb_add(ptVulkanGraphics->sbtTexturesHot); + } + + plTextureHandle tHandle = { + .uGeneration = ++ptGraphics->sbtTextureGenerations[uTextureIndex], + .uIndex = uTextureIndex + }; + + plTexture tTexture = { + .tDesc = tDesc, + .tView = { + .tFormat = tDesc.tFormat, + .uBaseMip = 0, + .uMips = tDesc.uMips, + .uBaseLayer = 0, + .uLayerCount = tDesc.uLayers, + .tTexture = tHandle + }, + ._tDrawBindGroup = {.ulData = UINT64_MAX} + }; + + plVulkanTexture tVulkanTexture = { + .bOriginalView = true + }; + + VkImageViewType tImageViewType = 0; + if(tDesc.tType == PL_TEXTURE_TYPE_CUBE) + tImageViewType = VK_IMAGE_VIEW_TYPE_CUBE; + else if(tDesc.tType == PL_TEXTURE_TYPE_2D) + tImageViewType = VK_IMAGE_VIEW_TYPE_2D; + else if(tDesc.tType == PL_TEXTURE_TYPE_2D_ARRAY) + tImageViewType = VK_IMAGE_VIEW_TYPE_2D_ARRAY; + else + { + PL_ASSERT(false && "unsupported texture type"); + } + + VkImageUsageFlags tUsageFlags = 0; + if(tDesc.tUsage & PL_TEXTURE_USAGE_SAMPLED) tUsageFlags |= VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT; + if(tDesc.tUsage & PL_TEXTURE_USAGE_COLOR_ATTACHMENT) tUsageFlags |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT; + if(tDesc.tUsage & PL_TEXTURE_USAGE_DEPTH_STENCIL_ATTACHMENT) tUsageFlags |= VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT; + if(tDesc.tUsage & PL_TEXTURE_USAGE_TRANSIENT_ATTACHMENT) tUsageFlags |= VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT; + if(tDesc.tUsage & PL_TEXTURE_USAGE_INPUT_ATTACHMENT) tUsageFlags |= VK_IMAGE_USAGE_INPUT_ATTACHMENT_BIT; + + // create vulkan image + VkImageCreateInfo tImageInfo = { + .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, + .imageType = VK_IMAGE_TYPE_2D, + .extent.width = (uint32_t)tDesc.tDimensions.x, + .extent.height = (uint32_t)tDesc.tDimensions.y, + .extent.depth = (uint32_t)tDesc.tDimensions.z, + .mipLevels = tDesc.uMips, + .arrayLayers = tDesc.uLayers, + .format = pl__vulkan_format(tDesc.tFormat), + .tiling = VK_IMAGE_TILING_OPTIMAL, + .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, + .usage = tUsageFlags, + .sharingMode = VK_SHARING_MODE_EXCLUSIVE, + .samples = 1, + .flags = tImageViewType == VK_IMAGE_VIEW_TYPE_CUBE ? VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT : 0 + }; + + PL_VULKAN(vkCreateImage(ptVulkanDevice->tLogicalDevice, &tImageInfo, NULL, &tVulkanTexture.tImage)); + + // get memory requirements + VkMemoryRequirements tMemoryRequirements = {0}; + vkGetImageMemoryRequirements(ptVulkanDevice->tLogicalDevice, tVulkanTexture.tImage, &tMemoryRequirements); + tTexture.tMemoryRequirements.ulSize = tMemoryRequirements.size; + tTexture.tMemoryRequirements.ulAlignment = tMemoryRequirements.alignment; + tTexture.tMemoryRequirements.uMemoryTypeBits = tMemoryRequirements.memoryTypeBits; + + ptVulkanGraphics->sbtTexturesHot[uTextureIndex] = tVulkanTexture; + ptGraphics->sbtTexturesCold[uTextureIndex] = tTexture; + return tHandle; +} + +static void +pl_bind_texture_to_memory(plDevice* ptDevice, plTextureHandle tHandle, const plDeviceMemoryAllocation* ptAllocation) +{ + plVulkanDevice* ptVulkanDevice = ptDevice->_pInternalData; + plVulkanGraphics* ptVulkanGraphics = ptDevice->ptGraphics->_pInternalData; + plGraphics* ptGraphics = ptDevice->ptGraphics; + + plTexture* ptTexture = &ptGraphics->sbtTexturesCold[tHandle.uIndex]; + ptTexture->tMemoryAllocation = *ptAllocation; + plVulkanTexture* ptVulkanTexture = &ptVulkanGraphics->sbtTexturesHot[tHandle.uIndex]; + + PL_VULKAN(vkBindImageMemory(ptVulkanDevice->tLogicalDevice, ptVulkanTexture->tImage, (VkDeviceMemory)ptAllocation->uHandle, ptAllocation->ulOffset)); + + VkImageAspectFlags tImageAspectFlags = ptTexture->tDesc.tUsage & PL_TEXTURE_USAGE_DEPTH_STENCIL_ATTACHMENT ? VK_IMAGE_ASPECT_DEPTH_BIT : VK_IMAGE_ASPECT_COLOR_BIT; + + if(pl__format_has_stencil(pl__vulkan_format(ptTexture->tDesc.tFormat))) + tImageAspectFlags |= VK_IMAGE_ASPECT_STENCIL_BIT; + + VkCommandBuffer tCommandBuffer = {0}; + + const VkCommandBufferAllocateInfo tAllocInfo = { + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, + .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY, + .commandPool = ptVulkanDevice->tCmdPool, + .commandBufferCount = 1u, + }; + vkAllocateCommandBuffers(ptVulkanDevice->tLogicalDevice, &tAllocInfo, &tCommandBuffer); + + const VkCommandBufferBeginInfo tBeginInfo = { + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, + .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT + }; + + vkBeginCommandBuffer(tCommandBuffer, &tBeginInfo); - plVulkanBindGroup tVulkanBindGroup = { - .tDescriptorSetLayout = tDescriptorSetLayout + VkImageSubresourceRange tRange = { + .baseMipLevel = 0, + .levelCount = ptTexture->tDesc.uMips, + .baseArrayLayer = 0, + .layerCount = ptTexture->tDesc.uLayers, + .aspectMask = tImageAspectFlags }; - VkDescriptorSetVariableDescriptorCountAllocateInfoEXT variableDescriptorCountAllocInfo = { - .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_VARIABLE_DESCRIPTOR_COUNT_ALLOCATE_INFO_EXT, - .descriptorSetCount = 1, - .pDescriptorCounts = &tDescriptorCount, + pl__transition_image_layout(tCommandBuffer, ptVulkanTexture->tImage, VK_IMAGE_LAYOUT_UNDEFINED, pl__vulkan_layout(ptTexture->tDesc.tInitialUsage), tRange, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT); + + PL_VULKAN(vkEndCommandBuffer(tCommandBuffer)); + const VkSubmitInfo tSubmitInfo = { + .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, + .commandBufferCount = 1u, + .pCommandBuffers = &tCommandBuffer, }; - // allocate descriptor sets - const VkDescriptorSetAllocateInfo tAllocInfo = { - .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO, - .descriptorPool = ptCurrentFrame->tDynamicDescriptorPool, - .descriptorSetCount = 1, - .pSetLayouts = &tDescriptorSetLayout, - .pNext = bHasVariableDescriptors ? &variableDescriptorCountAllocInfo : NULL + PL_VULKAN(vkQueueSubmit(ptVulkanDevice->tGraphicsQueue, 1, &tSubmitInfo, VK_NULL_HANDLE)); + PL_VULKAN(vkDeviceWaitIdle(ptVulkanDevice->tLogicalDevice)); + vkFreeCommandBuffers(ptVulkanDevice->tLogicalDevice, ptVulkanDevice->tCmdPool, 1, &tCommandBuffer); + + //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~create view~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + VkImageViewType tImageViewType = 0; + if(ptTexture->tDesc.tType == PL_TEXTURE_TYPE_CUBE) + tImageViewType = VK_IMAGE_VIEW_TYPE_CUBE; + else if(ptTexture->tDesc.tType == PL_TEXTURE_TYPE_2D) + tImageViewType = VK_IMAGE_VIEW_TYPE_2D; + else if(ptTexture->tDesc.tType == PL_TEXTURE_TYPE_2D_ARRAY) + tImageViewType = VK_IMAGE_VIEW_TYPE_2D_ARRAY; + else + { + PL_ASSERT(false && "unsupported texture type"); + } + + VkImageViewCreateInfo tViewInfo = { + .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, + .image = ptVulkanTexture->tImage, + .viewType = tImageViewType, + .format = pl__vulkan_format(ptTexture->tDesc.tFormat), + .subresourceRange.baseMipLevel = ptTexture->tView.uBaseMip, + .subresourceRange.levelCount = ptTexture->tDesc.uMips, + .subresourceRange.baseArrayLayer = ptTexture->tView.uBaseLayer, + .subresourceRange.layerCount = ptTexture->tView.uLayerCount, + .subresourceRange.aspectMask = ptTexture->tDesc.tUsage & PL_TEXTURE_USAGE_DEPTH_STENCIL_ATTACHMENT ? VK_IMAGE_ASPECT_DEPTH_BIT : VK_IMAGE_ASPECT_COLOR_BIT, }; + PL_VULKAN(vkCreateImageView(ptVulkanDevice->tLogicalDevice, &tViewInfo, NULL, &ptVulkanTexture->tImageView)); - PL_VULKAN(vkAllocateDescriptorSets(ptVulkanDevice->tLogicalDevice, &tAllocInfo, &tVulkanBindGroup.tDescriptorSet)); + if(ptTexture->tDesc.tUsage & PL_TEXTURE_USAGE_SAMPLED) + { + if(pl_sb_size(ptGraphics->sbtFreeDrawBindGroups) == 0) + { + const plBindGroupLayout tDrawingBindGroup = { + .uTextureBindingCount = 1, + .atTextureBindings = { + {.uSlot = 0, .tStages = PL_STAGE_PIXEL, .tType = PL_TEXTURE_BINDING_TYPE_SAMPLED} + } + }; + ptGraphics->sbtTexturesCold[tHandle.uIndex]._tDrawBindGroup = pl_create_bind_group(ptDevice, &tDrawingBindGroup, "draw binding"); + } + else + { + ptGraphics->sbtTexturesCold[tHandle.uIndex]._tDrawBindGroup = pl_sb_pop(ptGraphics->sbtFreeDrawBindGroups); + } - ptVulkanGraphics->sbtBindGroupsHot[uBindGroupIndex] = tVulkanBindGroup; - ptGraphics->sbtBindGroupsCold[uBindGroupIndex] = tBindGroup; - pl_queue_bind_group_for_deletion(ptDevice, tHandle); - return tHandle; + const plBindGroupUpdateTextureData atBGTextureData[] = { + { + .tTexture = tHandle, + .uSlot = 0, + .tType = PL_TEXTURE_BINDING_TYPE_SAMPLED + } + }; + const plBindGroupUpdateData tBGData = { + .uTextureCount = 1, + .atTextures = atBGTextureData + }; + pl_update_bind_group(&ptGraphics->tDevice, ptGraphics->sbtTexturesCold[tHandle.uIndex]._tDrawBindGroup, &tBGData); + } } -static void -pl_update_bind_group(plDevice* ptDevice, plBindGroupHandle tHandle, const plBindGroupUpdateData* ptData) +static plTextureHandle +pl_create_texture_view(plDevice* ptDevice, const plTextureViewDesc* ptViewDesc, const char* pcName) { plGraphics* ptGraphics = ptDevice->ptGraphics; plVulkanDevice* ptVulkanDevice = ptDevice->_pInternalData; plVulkanGraphics* ptVulkanGraphics = ptGraphics->_pInternalData; - plBindGroup* ptBindGroup = &ptGraphics->sbtBindGroupsCold[tHandle.uIndex]; - plVulkanBindGroup* ptVulkanBindGroup = &ptVulkanGraphics->sbtBindGroupsHot[tHandle.uIndex]; - - VkWriteDescriptorSet* sbtWrites = pl_temp_allocator_alloc(&ptVulkanGraphics->tTempAllocator, (ptData->uBufferCount + ptData->uSamplerCount + ptData->uTextureCount) * sizeof(VkWriteDescriptorSet)); - - VkDescriptorBufferInfo* sbtBufferDescInfos = ptData->uBufferCount > 0 ? pl_temp_allocator_alloc(&ptVulkanGraphics->tTempAllocator, ptData->uBufferCount * sizeof(VkDescriptorBufferInfo)) : NULL; - VkDescriptorImageInfo* sbtImageDescInfos = ptData->uTextureCount > 0 ? pl_temp_allocator_alloc(&ptVulkanGraphics->tTempAllocator, ptData->uTextureCount * sizeof(VkDescriptorImageInfo)) : NULL; - VkDescriptorImageInfo* sbtSamplerDescInfos = ptData->uSamplerCount > 0 ? pl_temp_allocator_alloc(&ptVulkanGraphics->tTempAllocator, ptData->uSamplerCount * sizeof(VkDescriptorImageInfo)) : NULL; - - static const VkDescriptorType atDescriptorTypeLUT[] = + uint32_t uTextureIndex = UINT32_MAX; + if(pl_sb_size(ptGraphics->sbtTextureFreeIndices) > 0) + uTextureIndex = pl_sb_pop(ptGraphics->sbtTextureFreeIndices); + else { - VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, - VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, + uTextureIndex = pl_sb_size(ptGraphics->sbtTexturesCold); + pl_sb_add(ptGraphics->sbtTexturesCold); + pl_sb_push(ptGraphics->sbtTextureGenerations, UINT32_MAX); + pl_sb_add(ptVulkanGraphics->sbtTexturesHot); + } + + plTextureHandle tHandle = { + .uGeneration = ++ptGraphics->sbtTextureGenerations[uTextureIndex], + .uIndex = uTextureIndex }; - uint32_t uCurrentWrite = 0; - for(uint32_t i = 0 ; i < ptData->uBufferCount; i++) - { + plTexture tTexture = { + .tDesc = ptGraphics->sbtTexturesCold[ptViewDesc->tTexture.uIndex].tDesc, + .tView = *ptViewDesc, + ._tDrawBindGroup = {.ulData = UINT64_MAX} + }; - const plVulkanBuffer* ptVulkanBuffer = &ptVulkanGraphics->sbtBuffersHot[ptData->atBuffers[i].tBuffer.uIndex]; + plTexture* ptTexture = pl__get_texture(ptDevice, ptViewDesc->tTexture); + plVulkanTexture* ptOldVulkanTexture = &ptVulkanGraphics->sbtTexturesHot[ptViewDesc->tTexture.uIndex]; + plVulkanTexture* ptNewVulkanTexture = &ptVulkanGraphics->sbtTexturesHot[tHandle.uIndex]; - sbtBufferDescInfos[i].buffer = ptVulkanBuffer->tBuffer; - sbtBufferDescInfos[i].offset = ptData->atBuffers[i].szOffset; - sbtBufferDescInfos[i].range = ptData->atBuffers[i].szBufferRange; + //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~create view~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - sbtWrites[uCurrentWrite].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; - sbtWrites[uCurrentWrite].dstBinding = ptData->atBuffers[i].uSlot; - sbtWrites[uCurrentWrite].dstArrayElement = 0; - sbtWrites[uCurrentWrite].descriptorType = atDescriptorTypeLUT[ptBindGroup->tLayout.aBufferBindings[i].tType - 1]; - sbtWrites[uCurrentWrite].descriptorCount = 1; - sbtWrites[uCurrentWrite].dstSet = ptVulkanBindGroup->tDescriptorSet; - sbtWrites[uCurrentWrite].pBufferInfo = &sbtBufferDescInfos[i]; - sbtWrites[uCurrentWrite].pNext = NULL; - uCurrentWrite++; + VkImageViewType tImageViewType = 0; + if(ptTexture->tDesc.tType == PL_TEXTURE_TYPE_CUBE) + tImageViewType = VK_IMAGE_VIEW_TYPE_CUBE; + else if(ptTexture->tDesc.tType == PL_TEXTURE_TYPE_2D) + tImageViewType = VK_IMAGE_VIEW_TYPE_2D; + else if(ptTexture->tDesc.tType == PL_TEXTURE_TYPE_2D_ARRAY) + tImageViewType = VK_IMAGE_VIEW_TYPE_2D; + // tImageViewType = VK_IMAGE_VIEW_TYPE_2D_ARRAY; + else + { + PL_ASSERT(false && "unsupported texture type"); } - static const VkDescriptorType atTextureDescriptorTypeLUT[] = - { - VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, - VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT, - }; - for(uint32_t i = 0 ; i < ptData->uTextureCount; i++) - { + VkImageAspectFlags tImageAspectFlags = ptTexture->tDesc.tUsage & PL_TEXTURE_USAGE_DEPTH_STENCIL_ATTACHMENT ? VK_IMAGE_ASPECT_DEPTH_BIT : VK_IMAGE_ASPECT_COLOR_BIT; - sbtImageDescInfos[i].imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; - sbtImageDescInfos[i].imageView = ptVulkanGraphics->sbtTexturesHot[ptData->atTextures[i].tTexture.uIndex].tImageView; - sbtWrites[uCurrentWrite].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; - sbtWrites[uCurrentWrite].dstBinding = ptData->atTextures[i].uSlot; - sbtWrites[uCurrentWrite].dstArrayElement = ptData->atTextures[i].uIndex; - sbtWrites[uCurrentWrite].descriptorType = atTextureDescriptorTypeLUT[ptData->atTextures[i].tType - 1]; - sbtWrites[uCurrentWrite].descriptorCount = 1; - sbtWrites[uCurrentWrite].dstSet = ptVulkanBindGroup->tDescriptorSet; - sbtWrites[uCurrentWrite].pImageInfo = &sbtImageDescInfos[i]; - sbtWrites[uCurrentWrite].pNext = NULL; - uCurrentWrite++; - } + VkImageViewCreateInfo tViewInfo = { + .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, + .image = ptOldVulkanTexture->tImage, + .viewType = tImageViewType, + .format = pl__vulkan_format(ptViewDesc->tFormat), + .subresourceRange.baseMipLevel = ptViewDesc->uBaseMip, + .subresourceRange.levelCount = ptViewDesc->uMips == 0 ? ptTexture->tDesc.uMips - ptViewDesc->uBaseMip : ptViewDesc->uMips, + .subresourceRange.baseArrayLayer = ptViewDesc->uBaseLayer, + .subresourceRange.layerCount = ptViewDesc->uLayerCount, + .subresourceRange.aspectMask = tImageAspectFlags, + }; + PL_VULKAN(vkCreateImageView(ptVulkanDevice->tLogicalDevice, &tViewInfo, NULL, &ptNewVulkanTexture->tImageView)); - for(uint32_t i = 0 ; i < ptData->uSamplerCount; i++) + if(ptTexture->tDesc.tUsage & PL_TEXTURE_USAGE_SAMPLED) { + if(pl_sb_size(ptGraphics->sbtFreeDrawBindGroups) == 0) + { + const plBindGroupLayout tDrawingBindGroup = { + .uTextureBindingCount = 1, + .atTextureBindings = { + {.uSlot = 0, .tStages = PL_STAGE_PIXEL, .tType = PL_TEXTURE_BINDING_TYPE_SAMPLED} + } + }; + ptGraphics->sbtTexturesCold[tHandle.uIndex]._tDrawBindGroup = pl_create_bind_group(ptDevice, &tDrawingBindGroup, "draw binding"); + } + else + { + ptGraphics->sbtTexturesCold[tHandle.uIndex]._tDrawBindGroup = pl_sb_pop(ptGraphics->sbtFreeDrawBindGroups); + } - sbtSamplerDescInfos[i].sampler = ptVulkanGraphics->sbtSamplersHot[ptData->atSamplerBindings[i].tSampler.uIndex]; - sbtWrites[uCurrentWrite].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; - sbtWrites[uCurrentWrite].dstBinding = ptData->atSamplerBindings[i].uSlot; - sbtWrites[uCurrentWrite].dstArrayElement = 0; - sbtWrites[uCurrentWrite].descriptorType = VK_DESCRIPTOR_TYPE_SAMPLER; - sbtWrites[uCurrentWrite].descriptorCount = 1; - sbtWrites[uCurrentWrite].dstSet = ptVulkanBindGroup->tDescriptorSet; - sbtWrites[uCurrentWrite].pImageInfo = &sbtSamplerDescInfos[i]; - sbtWrites[uCurrentWrite].pNext = NULL; - uCurrentWrite++; + const plBindGroupUpdateTextureData atBGTextureData[] = { + { + .tTexture = tHandle, + .uSlot = 0, + .tType = PL_TEXTURE_BINDING_TYPE_SAMPLED + } + }; + const plBindGroupUpdateData tBGData = { + .uTextureCount = 1, + .atTextures = atBGTextureData + }; + pl_update_bind_group(&ptGraphics->tDevice, ptGraphics->sbtTexturesCold[tHandle.uIndex]._tDrawBindGroup, &tBGData); } - vkUpdateDescriptorSets(ptVulkanDevice->tLogicalDevice, uCurrentWrite, sbtWrites, 0, NULL); - pl_temp_allocator_reset(&ptVulkanGraphics->tTempAllocator); + ptNewVulkanTexture->bOriginalView = false; + ptGraphics->sbtTexturesCold[uTextureIndex] = tTexture; + return tHandle; } static plComputeShaderHandle @@ -1944,12 +1998,37 @@ pl_create_main_render_pass_layout(plDevice* ptDevice) .pColorAttachments = &tColorReference }; + const VkSubpassDependency tSubpassDependencies[] = { + + { + .srcSubpass = VK_SUBPASS_EXTERNAL, + .dstSubpass = 0, + .srcStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, + .dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, + .srcAccessMask = VK_ACCESS_SHADER_READ_BIT, + .dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, + .dependencyFlags = 0 + }, + + { + .srcSubpass = 0, + .dstSubpass = VK_SUBPASS_EXTERNAL, + .srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, + .dstStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, + .srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, + .dstAccessMask = VK_ACCESS_SHADER_READ_BIT, + .dependencyFlags = 0 + } + }; + const VkRenderPassCreateInfo tRenderPassInfo = { .sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO, .attachmentCount = 1, .pAttachments = &tAttachment, .subpassCount = 1, - .pSubpasses = &tSubpass + .pSubpasses = &tSubpass, + .pDependencies = tSubpassDependencies, + .dependencyCount = 2 }; PL_VULKAN(vkCreateRenderPass(ptVulkanDevice->tLogicalDevice, &tRenderPassInfo, NULL, &tVulkanRenderPassLayout.tRenderPass)); @@ -1984,7 +2063,8 @@ pl_create_main_render_pass(plDevice* ptDevice) plRenderPass tRenderPass = { .tDesc = { - .tDimensions = {pl_get_io()->afMainViewportSize[0], pl_get_io()->afMainViewportSize[1]} + .tDimensions = {gptIO->get_io()->afMainViewportSize[0], gptIO->get_io()->afMainViewportSize[1]}, + .tLayout = ptGraphics->tMainRenderPassLayout }, .bSwapchain = true }; @@ -2056,8 +2136,8 @@ pl_create_main_render_pass(plDevice* ptDevice) .renderPass = ptVulkanRenderPass->tRenderPass, .attachmentCount = 1, .pAttachments = &ptVulkanGfx->sbtTexturesHot[ptGraphics->tSwapchain.sbtSwapchainTextureViews[i].uIndex].tImageView, - .width = (uint32_t)pl_get_io()->afMainViewportSize[0], - .height = (uint32_t)pl_get_io()->afMainViewportSize[1], + .width = (uint32_t)gptIO->get_io()->afMainViewportSize[0], + .height = (uint32_t)gptIO->get_io()->afMainViewportSize[1], .layers = 1u, }; PL_VULKAN(vkCreateFramebuffer(ptVulkanDevice->tLogicalDevice, &tFrameBufferInfo, NULL, &ptVulkanRenderPass->atFrameBuffers[i])); @@ -2800,7 +2880,7 @@ pl_draw_stream(plRenderEncoder* ptEncoder, uint32_t uAreaCount, plDrawArea* atAr } static void -pl_set_viewport(plRenderEncoder* ptEncoder, plRenderViewport* ptViewport) +pl_set_viewport(plRenderEncoder* ptEncoder, const plRenderViewport* ptViewport) { VkCommandBuffer tCmdBuffer = (VkCommandBuffer)ptEncoder->tCommandBuffer._pInternal; @@ -2817,7 +2897,7 @@ pl_set_viewport(plRenderEncoder* ptEncoder, plRenderViewport* ptViewport) } static void -pl_set_scissor_region(plRenderEncoder* ptEncoder, plScissor* ptScissor) +pl_set_scissor_region(plRenderEncoder* ptEncoder, const plScissor* ptScissor) { VkCommandBuffer tCmdBuffer = (VkCommandBuffer)ptEncoder->tCommandBuffer._pInternal; @@ -2835,28 +2915,6 @@ pl_set_scissor_region(plRenderEncoder* ptEncoder, plScissor* ptScissor) vkCmdSetScissor(tCmdBuffer, 0, 1, &tScissor); } -static void -pl_draw_list(plGraphics* ptGraphics, plRenderEncoder tEncoder, uint32_t uListCount, plDrawList* atLists) -{ - VkCommandBuffer tCmdBuffer = (VkCommandBuffer)tEncoder.tCommandBuffer._pInternal; - plVulkanGraphics* ptVulkanGfx = ptGraphics->_pInternalData; - plVulkanDevice* ptVulkanDevice = ptGraphics->tDevice._pInternalData; - - plFrameContext* ptCurrentFrame = pl__get_frame_resources(ptGraphics); - - plIO* ptIOCtx = pl_get_io(); - for(uint32_t i = 0; i < uListCount; i++) - { - pl_submit_vulkan_drawlist_ex(&atLists[i], - ptIOCtx->afMainViewportSize[0], - ptIOCtx->afMainViewportSize[1], - tCmdBuffer, - ptGraphics->uCurrentFrameIndex, - ptVulkanGfx->sbtRenderPassesHot[tEncoder.tRenderPassHandle.uIndex].tRenderPass, - VK_SAMPLE_COUNT_1_BIT); - } -} - typedef struct _plInternalDeviceAllocatorData { plDevice* ptDevice; @@ -2878,8 +2936,8 @@ pl_allocate_staging_dynamic(struct plDeviceMemoryAllocatorO* ptInst, uint32_t uT }; - plDeviceAllocationBlock tBlock = pl_allocate_memory(ptData->ptDevice, ulSize, PL_MEMORY_GPU_CPU, uTypeFilter, "dynamic uncached Heap"); - tAllocation.uHandle = tBlock.ulAddress; + plDeviceMemoryAllocation tBlock = pl_allocate_memory(ptData->ptDevice, ulSize, PL_MEMORY_GPU_CPU, uTypeFilter, "dynamic uncached Heap"); + tAllocation.uHandle = tBlock.uHandle; tAllocation.pHostMapped = tBlock.pHostMapped; ptData->ptDevice->ptGraphics->szHostMemoryInUse += ulSize; return tAllocation; @@ -2889,7 +2947,7 @@ static void pl_free_staging_dynamic(struct plDeviceMemoryAllocatorO* ptInst, plDeviceMemoryAllocation* ptAllocation) { plInternalDeviceAllocatorData* ptData = (plInternalDeviceAllocatorData*)ptInst; - plDeviceAllocationBlock tBlock = {.ulAddress = ptAllocation->uHandle}; + plDeviceMemoryAllocation tBlock = {.uHandle = ptAllocation->uHandle}; pl_free_memory(ptData->ptDevice, &tBlock); ptData->ptDevice->ptGraphics->szHostMemoryInUse -= ptAllocation->ulSize; ptAllocation->uHandle = 0; @@ -2903,7 +2961,7 @@ pl_initialize_graphics(plWindow* ptWindow, const plGraphicsDesc* ptDesc, plGraph ptGraphics->bValidationActive = ptDesc->bEnableValidation; ptGraphics->ptMainWindow = ptWindow; - plIO* ptIOCtx = pl_get_io(); + plIO* ptIOCtx = gptIO->get_io(); ptGraphics->_pInternalData = PL_ALLOC(sizeof(plVulkanGraphics)); memset(ptGraphics->_pInternalData, 0, sizeof(plVulkanGraphics)); @@ -3468,31 +3526,11 @@ pl_initialize_graphics(plWindow* ptWindow, const plGraphicsDesc* ptDesc, plGraph pl_create_main_render_pass(&ptGraphics->tDevice); } -static void -pl_setup_ui(plGraphics* ptGraphics, plRenderPassHandle tPass) -{ - plVulkanGraphics* ptVulkanGfx = ptGraphics->_pInternalData; - plVulkanDevice* ptVulkanDevice = ptGraphics->tDevice._pInternalData; - plIO* ptIo = pl_get_io(); - ptIo->pBackendPlatformData = ptGraphics->ptMainWindow->_pPlatformData; - - // setup drawing api - const plVulkanInit tVulkanInit = { - .tPhysicalDevice = ptVulkanDevice->tPhysicalDevice, - .tLogicalDevice = ptVulkanDevice->tLogicalDevice, - .uImageCount = ptGraphics->tSwapchain.uImageCount, - .tRenderPass = ptVulkanGfx->sbtRenderPassesHot[tPass.uIndex].tRenderPass, - .tMSAASampleCount = VK_SAMPLE_COUNT_1_BIT, - .uFramesInFlight = ptGraphics->uFramesInFlight - }; - pl_initialize_vulkan(&tVulkanInit); -} - static bool pl_begin_frame(plGraphics* ptGraphics) { pl_begin_profile_sample(__FUNCTION__); - plIO* ptIOCtx = pl_get_io(); + plIO* ptIOCtx = gptIO->get_io(); plVulkanGraphics* ptVulkanGfx = ptGraphics->_pInternalData; plVulkanDevice* ptVulkanDevice = ptGraphics->tDevice._pInternalData; @@ -3530,8 +3568,6 @@ pl_begin_frame(plGraphics* ptGraphics) PL_VULKAN(vkResetCommandPool(ptVulkanDevice->tLogicalDevice, ptCurrentFrame->tCmdPool, VK_COMMAND_POOL_RESET_RELEASE_RESOURCES_BIT)); PL_VULKAN(vkResetCommandPool(ptVulkanDevice->tLogicalDevice, ptVulkanDevice->tCmdPool, VK_COMMAND_POOL_RESET_RELEASE_RESOURCES_BIT)); - pl_new_draw_frame_vulkan(); - pl_end_profile_sample(); return true; } @@ -3547,7 +3583,7 @@ static bool pl_present(plGraphics* ptGraphics, plCommandBuffer* ptCmdBuffer, const plSubmitInfo* ptSubmitInfo) { pl_begin_profile_sample(__FUNCTION__); - plIO* ptIOCtx = pl_get_io(); + plIO* ptIOCtx = gptIO->get_io(); VkCommandBuffer tCmdBuffer = (VkCommandBuffer)ptCmdBuffer->_pInternal; @@ -3648,7 +3684,7 @@ pl_resize(plGraphics* ptGraphics) pl_begin_profile_sample(__FUNCTION__); plVulkanGraphics* ptVulkanGfx = ptGraphics->_pInternalData; plVulkanDevice* ptVulkanDevice = ptGraphics->tDevice._pInternalData; - plIO* ptIOCtx = pl_get_io(); + plIO* ptIOCtx = gptIO->get_io(); pl__create_swapchain(ptGraphics, (uint32_t)ptIOCtx->afMainViewportSize[0], (uint32_t)ptIOCtx->afMainViewportSize[1]); @@ -3694,8 +3730,6 @@ pl_shutdown(plGraphics* ptGraphics) vkDeviceWaitIdle(ptVulkanDevice->tLogicalDevice); - pl_cleanup_vulkan(); - for(uint32_t i = 0; i < pl_sb_size(ptVulkanGfx->sbtTexturesHot); i++) { if(ptVulkanGfx->sbtTexturesHot[i].tImage && ptVulkanGfx->sbtTexturesHot[i].bOriginalView) @@ -4880,7 +4914,7 @@ pl__pilotlight_format(VkFormat tFormat) // [SECTION] device memory allocators //----------------------------------------------------------------------------- -static plDeviceAllocationBlock +static plDeviceMemoryAllocation pl_allocate_memory(plDevice* ptDevice, size_t szSize, plMemoryMode tMemoryMode, uint32_t uTypeFilter, const char* pcName) { plVulkanDevice* ptVulkanDevice = ptDevice->_pInternalData; @@ -4910,10 +4944,11 @@ pl_allocate_memory(plDevice* ptDevice, size_t szSize, plMemoryMode tMemoryMode, pcName = "unnamed memory block"; } - plDeviceAllocationBlock tBlock = { - .ulAddress = 0, + plDeviceMemoryAllocation tBlock = { + .uHandle = 0, .ulSize = (uint64_t)szSize, - .ulMemoryType = (uint64_t)uMemoryType + .ulMemoryType = (uint64_t)uMemoryType, + .tMemoryMode = tMemoryMode }; const VkMemoryAllocateInfo tAllocInfo = { .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, @@ -4924,9 +4959,9 @@ pl_allocate_memory(plDevice* ptDevice, size_t szSize, plMemoryMode tMemoryMode, VkDeviceMemory tMemory = VK_NULL_HANDLE; VkResult tResult = vkAllocateMemory(ptVulkanDevice->tLogicalDevice, &tAllocInfo, NULL, &tMemory); PL_VULKAN(tResult); - tBlock.ulAddress = (uint64_t)tMemory; + tBlock.uHandle = (uint64_t)tMemory; - pl_set_vulkan_object_name(ptDevice, tBlock.ulAddress, VK_DEBUG_REPORT_OBJECT_TYPE_DEVICE_MEMORY_EXT, pcName); + pl_set_vulkan_object_name(ptDevice, tBlock.uHandle, VK_DEBUG_REPORT_OBJECT_TYPE_DEVICE_MEMORY_EXT, pcName); if(tMemoryMode == PL_MEMORY_GPU) { @@ -4934,7 +4969,7 @@ pl_allocate_memory(plDevice* ptDevice, size_t szSize, plMemoryMode tMemoryMode, } else { - PL_VULKAN(vkMapMemory(ptVulkanDevice->tLogicalDevice, (VkDeviceMemory)tBlock.ulAddress, 0, tBlock.ulSize, 0, (void**)&tBlock.pHostMapped)); + PL_VULKAN(vkMapMemory(ptVulkanDevice->tLogicalDevice, (VkDeviceMemory)tBlock.uHandle, 0, tBlock.ulSize, 0, (void**)&tBlock.pHostMapped)); ptDevice->ptGraphics->szHostMemoryInUse += tBlock.ulSize; } @@ -4942,7 +4977,7 @@ pl_allocate_memory(plDevice* ptDevice, size_t szSize, plMemoryMode tMemoryMode, } static void -pl_free_memory(plDevice* ptDevice, plDeviceAllocationBlock* ptBlock) +pl_free_memory(plDevice* ptDevice, plDeviceMemoryAllocation* ptBlock) { plVulkanDevice* ptVulkanDevice = ptDevice->_pInternalData; @@ -4955,13 +4990,12 @@ pl_free_memory(plDevice* ptDevice, plDeviceAllocationBlock* ptBlock) ptDevice->ptGraphics->szHostMemoryInUse -= ptBlock->ulSize; } - vkFreeMemory(ptVulkanDevice->tLogicalDevice, (VkDeviceMemory)ptBlock->ulAddress, NULL); - ptBlock->ulAddress = 0; + vkFreeMemory(ptVulkanDevice->tLogicalDevice, (VkDeviceMemory)ptBlock->uHandle, NULL); + ptBlock->uHandle = 0; ptBlock->pHostMapped = NULL; ptBlock->ulSize = 0; ptBlock->tMemoryMode = 0; ptBlock->ulMemoryType = 0; - ptBlock->dLastTimeUsed = 0; } static void @@ -5106,6 +5140,8 @@ pl__garbage_collect(plGraphics* ptGraphics) plDeviceMemoryAllocatorI* ptAllocator = tAllocation.ptAllocator; if(ptAllocator) // swapchain doesn't have allocator since texture is provided ptAllocator->free(ptAllocator->ptInst, &tAllocation); + else + pl_free_memory(&ptGraphics->tDevice, &tAllocation); } pl_sb_reset(ptGarbage->sbtTextures); @@ -5133,7 +5169,10 @@ pl_destroy_buffer(plDevice* ptDevice, plBufferHandle tHandle) pl_sb_push(ptGraphics->sbtBufferFreeIndices, tHandle.uIndex); plBuffer* ptBuffer = &ptGraphics->sbtBuffersCold[tHandle.uIndex]; - ptBuffer->tMemoryAllocation.ptAllocator->free(ptBuffer->tMemoryAllocation.ptAllocator->ptInst, &ptBuffer->tMemoryAllocation); + if(ptBuffer->tMemoryAllocation.ptAllocator) + ptBuffer->tMemoryAllocation.ptAllocator->free(ptBuffer->tMemoryAllocation.ptAllocator->ptInst, &ptBuffer->tMemoryAllocation); + else + pl_free_memory(ptDevice, &ptBuffer->tMemoryAllocation); } static void @@ -5148,9 +5187,16 @@ pl_destroy_texture(plDevice* ptDevice, plTextureHandle tHandle) ptVulkanResource->tImage = VK_NULL_HANDLE; pl_sb_push(ptGraphics->sbtTextureFreeIndices, tHandle.uIndex); ptGraphics->sbtTextureGenerations[tHandle.uIndex]++; - + plTexture* ptTexture = &ptGraphics->sbtTexturesCold[tHandle.uIndex]; - ptTexture->tMemoryAllocation.ptAllocator->free(ptTexture->tMemoryAllocation.ptAllocator->ptInst, &ptTexture->tMemoryAllocation); + if(ptTexture->_tDrawBindGroup.ulData != UINT64_MAX) + { + pl_sb_push(ptGraphics->sbtFreeDrawBindGroups, ptTexture->_tDrawBindGroup); + } + if(ptTexture->tMemoryAllocation.ptAllocator) + ptTexture->tMemoryAllocation.ptAllocator->free(ptTexture->tMemoryAllocation.ptAllocator->ptInst, &ptTexture->tMemoryAllocation); + else + pl_free_memory(ptDevice, &ptTexture->tMemoryAllocation); } static void @@ -5276,7 +5322,6 @@ pl_load_graphics_api(void) static const plGraphicsI tApi = { .initialize = pl_initialize_graphics, .resize = pl_resize, - .setup_ui = pl_setup_ui, .begin_frame = pl_begin_frame, .dispatch = pl_dispatch, .bind_compute_bind_groups = pl_bind_compute_bind_groups, @@ -5286,11 +5331,7 @@ pl_load_graphics_api(void) .set_scissor_region = pl_set_scissor_region, .set_viewport = pl_set_viewport, .bind_vertex_buffer = pl_bind_vertex_buffer, - .draw_lists = pl_draw_list, .cleanup = pl_shutdown, - .create_font_atlas = pl_create_vulkan_font_texture, - .destroy_font_atlas = pl_cleanup_vulkan_font_texture, - .get_ui_texture_handle = pl_get_ui_texture_handle, .begin_command_recording = pl_begin_command_recording, .end_command_recording = pl_end_command_recording, .submit_command_buffer = pl_submit_command_buffer, @@ -5374,8 +5415,8 @@ pl_load_ext(plApiRegistryI* ptApiRegistry, bool bReload) pl_set_memory_context(ptDataRegistry->get_data(PL_CONTEXT_MEMORY)); pl_set_profile_context(ptDataRegistry->get_data("profile")); pl_set_log_context(ptDataRegistry->get_data("log")); - pl_set_context(ptDataRegistry->get_data("context")); gptFile = ptApiRegistry->first(PL_API_FILE); + gptIO = ptApiRegistry->first(PL_API_IO); if(bReload) { ptApiRegistry->replace(ptApiRegistry->first(PL_API_GRAPHICS), pl_load_graphics_api()); @@ -5405,10 +5446,4 @@ PL_EXPORT void pl_unload_ext(plApiRegistryI* ptApiRegistry) { -} - -//----------------------------------------------------------------------------- -// [SECTION] unity build -//----------------------------------------------------------------------------- - -#include "pl_ui_vulkan.c" +} \ No newline at end of file diff --git a/libs/pl_math.h b/libs/pl_math.h index 321442c5..12c7333d 100644 --- a/libs/pl_math.h +++ b/libs/pl_math.h @@ -77,8 +77,6 @@ typedef struct _plAABB plAABB; // [SECTION] structs //----------------------------------------------------------------------------- -#ifndef PL_MATH_VEC2_DEFINED -#define PL_MATH_VEC2_DEFINED typedef union _plVec2 { struct { float x, y; }; @@ -86,7 +84,6 @@ typedef union _plVec2 struct { float u, v; }; float d[2]; } plVec2; -#endif // PL_MATH_VEC2_DEFINED typedef union _plVec3 { diff --git a/libs/pl_memory.h b/libs/pl_memory.h index b8a3dbdd..fc181b3d 100644 --- a/libs/pl_memory.h +++ b/libs/pl_memory.h @@ -683,7 +683,9 @@ pl_pool_allocator_init(plPoolAllocator* ptAllocator, size_t szItemCount, size_t PL_ASSERT(pszBufferSize); if(szItemAlignment == 0) - szItemAlignment = szItemSize; + { + szItemAlignment = pl__get_next_power_of_2(szItemSize); + } if(pBuffer == NULL) { diff --git a/scripts/gen_build.py b/scripts/gen_build.py index d2b4bc49..399eac52 100644 --- a/scripts/gen_build.py +++ b/scripts/gen_build.py @@ -1,6 +1,5 @@ import os import sys -import shutil sys.path.append(os.path.dirname(os.path.abspath(__file__)) + "/../build") @@ -10,7 +9,7 @@ # helpers # ############################################################################### -def add_plugin_to_vulkan_app(name, reloadable, binary_name = None, *kwargs): +def add_plugin_to_vulkan_app(name, reloadable, binary_name = None, directory = "../extensions/", *kwargs): pl.push_profile(pl.Profile.VULKAN) pl.push_definitions("PL_VULKAN_BACKEND") @@ -19,9 +18,9 @@ def add_plugin_to_vulkan_app(name, reloadable, binary_name = None, *kwargs): pl.push_output_binary(name) else: pl.push_output_binary(binary_name) - source_files = ["../extensions/" + name + ".c"] + source_files = [directory + name + ".c"] for source in kwargs: - source_files.append("../extensions/" + source + ".c") + source_files.append(directory + source + ".c") pl.push_source_files(*source_files) with pl.configuration("debug"): with pl.platform(pl.PlatformType.WIN32): @@ -39,7 +38,7 @@ def add_plugin_to_vulkan_app(name, reloadable, binary_name = None, *kwargs): pl.pop_profile() pl.pop_definitions() -def add_plugin_to_metal_app(name, reloadable, objc = False, binary_name = None): +def add_plugin_to_metal_app(name, reloadable, objc = False, binary_name = None, directory = "../extensions/"): pl.push_definitions("PL_METAL_BACKEND") with pl.target(name, pl.TargetType.DYNAMIC_LIBRARY, reloadable): @@ -49,9 +48,9 @@ def add_plugin_to_metal_app(name, reloadable, objc = False, binary_name = None): else: pl.push_output_binary(binary_name) if objc: - pl.push_source_files("../extensions/" + name + ".m") + pl.push_source_files(directory + name + ".m") else: - pl.push_source_files("../extensions/" + name + ".c") + pl.push_source_files(directory + name + ".c") with pl.configuration("debug"): with pl.platform(pl.PlatformType.MACOS): with pl.compiler("clang", pl.CompilerType.CLANG): @@ -77,7 +76,7 @@ def add_plugin_to_metal_app(name, reloadable, objc = False, binary_name = None): pl.push_profile(pl.Profile.PILOT_LIGHT_DEBUG_C) pl.push_definitions("_USE_MATH_DEFINES", "PL_PROFILING_ON", "PL_ALLOW_HOT_RELOAD", "PL_ENABLE_VALIDATION_LAYERS") - pl.push_include_directories("../apps", "../src", "../libs", "../extensions", "../backends", "../out", "../dependencies/pilotlight-ui", "../dependencies/pilotlight-ui/backends", "../dependencies/stb", "../dependencies/cgltf") + pl.push_include_directories("../apps", "../src", "../ui", "../libs", "../extensions", "../out", "../dependencies/stb", "../dependencies/cgltf") pl.push_link_directories("../out") pl.push_output_directory("../out") @@ -115,8 +114,8 @@ def add_plugin_to_metal_app(name, reloadable, objc = False, binary_name = None): # plugins # ############################################################################### pl.push_target_links("pilotlight_lib") - - add_plugin_to_vulkan_app("pl_draw_3d_ext", True) + + add_plugin_to_vulkan_app("pl_draw_ext", True) add_plugin_to_vulkan_app("pl_debug_ext", False) add_plugin_to_vulkan_app("pl_image_ext", False) add_plugin_to_vulkan_app("pl_vulkan_ext", False, "pl_graphics_ext") @@ -127,8 +126,9 @@ def add_plugin_to_metal_app(name, reloadable, objc = False, binary_name = None): add_plugin_to_vulkan_app("pl_resource_ext", False) add_plugin_to_vulkan_app("pl_gpu_allocators_ext", False) add_plugin_to_vulkan_app("pl_ref_renderer_ext", True) + add_plugin_to_vulkan_app("pl_ui_ext", True, None, "../ui/") - add_plugin_to_metal_app("pl_draw_3d_ext", True) + add_plugin_to_metal_app("pl_draw_ext", True) add_plugin_to_metal_app("pl_debug_ext", False) add_plugin_to_metal_app("pl_image_ext", False) add_plugin_to_metal_app("pl_stats_ext", False) @@ -139,6 +139,7 @@ def add_plugin_to_metal_app(name, reloadable, objc = False, binary_name = None): add_plugin_to_metal_app("pl_metal_ext", False, True, "pl_graphics_ext") add_plugin_to_metal_app("pl_gpu_allocators_ext", False) add_plugin_to_metal_app("pl_ref_renderer_ext", True) + add_plugin_to_metal_app("pl_ui_ext", True, False, None, "../ui/") pl.pop_target_links() @@ -155,6 +156,9 @@ def add_plugin_to_metal_app(name, reloadable, objc = False, binary_name = None): "transparent.frag", "lighting.vert", "lighting.frag", + "draw_2d.vert", + "draw_2d.frag", + "draw_2d_sdf.frag", "draw_3d.vert", "draw_3d.frag", "draw_3d_line.vert", diff --git a/scripts/gen_examples.py b/scripts/gen_examples.py index b6da9ab3..941f6a24 100644 --- a/scripts/gen_examples.py +++ b/scripts/gen_examples.py @@ -51,7 +51,7 @@ def add_example_app(name): pl.push_profile(pl.Profile.PILOT_LIGHT_DEBUG_C) pl.push_definitions("_USE_MATH_DEFINES", "PL_PROFILING_ON", "PL_ALLOW_HOT_RELOAD", "PL_ENABLE_VALIDATION_LAYERS") - pl.push_include_directories("../apps", "../examples", "../src", "../libs", "../extensions", "../backends", "../out", "../dependencies/pilotlight-ui", "../dependencies/pilotlight-ui/backends", "../dependencies/stb", "../dependencies/cgltf") + pl.push_include_directories("../apps", "../examples", "../src", "../ui", "../libs", "../extensions", "../out", "../dependencies/stb", "../dependencies/cgltf") pl.push_link_directories("../out") pl.push_output_directory("../out") @@ -87,6 +87,7 @@ def add_example_app(name): add_example_app("example_1") add_example_app("example_2") add_example_app("example_3") + add_example_app("example_4") ############################################################################### # pilot_light # diff --git a/setup.py b/setup.py index 2a462be7..fc0ccc50 100644 --- a/setup.py +++ b/setup.py @@ -20,12 +20,11 @@ "${workspaceFolder}/**", "${workspaceFolder}/apps", "${workspaceFolder}/src", + "${workspaceFolder}/ui", + "${workspaceFolder}/libs", "${workspaceFolder}/extensions", "${workspaceFolder}/dependencies/stb", "${workspaceFolder}/dependencies/cgltf", - "${workspaceFolder}/dependencies/pilotlight-libs", - "${workspaceFolder}/dependencies/pilotlight-ui", - "${workspaceFolder}/dependencies/pilotlight-ui/backends", "${env:VK_SDK_PATH}/Include" ] @@ -63,7 +62,7 @@ lines.append(' "program": "${workspaceFolder}/out/pilot_light",') lines.append(' "request": "launch",') - lines.append(' "args": [],') + lines.append(' "args": ["-a", "app"],') lines.append(' "stopAtEntry": false,') lines.append(' "cwd": "${workspaceFolder}/out/",') lines.append(' "environment": []') diff --git a/shaders/glsl/draw_2d.frag b/shaders/glsl/draw_2d.frag new file mode 100644 index 00000000..d8885f4b --- /dev/null +++ b/shaders/glsl/draw_2d.frag @@ -0,0 +1,31 @@ +#version 450 core +#extension GL_ARB_separate_shader_objects : enable + +//----------------------------------------------------------------------------- +// [SECTION] bind group 0 +//----------------------------------------------------------------------------- + +layout(set = 0, binding = 0) uniform sampler tFontSampler; + +//----------------------------------------------------------------------------- +// [SECTION] bind group 1 +//----------------------------------------------------------------------------- + +layout(set = 1, binding = 0) uniform texture2D tFontAtlas; + +//----------------------------------------------------------------------------- +// [SECTION] input +//----------------------------------------------------------------------------- + +layout(location = 0) in struct { vec4 Color; vec2 UV; } In; + +//----------------------------------------------------------------------------- +// [SECTION] output +//----------------------------------------------------------------------------- + +layout(location = 0) out vec4 fColor; + +void main() +{ + fColor = In.Color * texture(sampler2D(tFontAtlas, tFontSampler), In.UV.st); +} \ No newline at end of file diff --git a/shaders/glsl/draw_2d.vert b/shaders/glsl/draw_2d.vert new file mode 100644 index 00000000..9c623d65 --- /dev/null +++ b/shaders/glsl/draw_2d.vert @@ -0,0 +1,33 @@ +#version 450 core +#extension GL_ARB_separate_shader_objects : enable + +//----------------------------------------------------------------------------- +// [SECTION] dynamic bind group +//----------------------------------------------------------------------------- + +layout(set = 2, binding = 0) uniform _plObjectInfo { + vec2 uScale; + vec2 uTranslate; +} tObjectInfo; + +//----------------------------------------------------------------------------- +// [SECTION] input +//----------------------------------------------------------------------------- + +layout(location = 0) in vec2 aPos; +layout(location = 1) in vec2 aUV; +layout(location = 2) in vec4 aColor; + +//----------------------------------------------------------------------------- +// [SECTION] output +//----------------------------------------------------------------------------- + +layout(location = 0) out struct { vec4 Color; vec2 UV; } Out; + + +void main() +{ + Out.Color = aColor; + Out.UV = aUV; + gl_Position = vec4(aPos * tObjectInfo.uScale + tObjectInfo.uTranslate, 0, 1); +} \ No newline at end of file diff --git a/shaders/glsl/draw_2d_sdf.frag b/shaders/glsl/draw_2d_sdf.frag new file mode 100644 index 00000000..c5174d8f --- /dev/null +++ b/shaders/glsl/draw_2d_sdf.frag @@ -0,0 +1,35 @@ +#version 450 core +#extension GL_ARB_separate_shader_objects : enable + +//----------------------------------------------------------------------------- +// [SECTION] bind group 0 +//----------------------------------------------------------------------------- + +layout(set = 0, binding = 0) uniform sampler tFontSampler; + +//----------------------------------------------------------------------------- +// [SECTION] bind group 1 +//----------------------------------------------------------------------------- + +layout(set = 1, binding = 0) uniform texture2D tFontAtlas; + +//----------------------------------------------------------------------------- +// [SECTION] input +//----------------------------------------------------------------------------- + +layout(location = 0) in struct { vec4 Color; vec2 UV; } In; + +//----------------------------------------------------------------------------- +// [SECTION] output +//----------------------------------------------------------------------------- + +layout(location = 0) out vec4 fColor; + +void main() +{ + float fDistance = texture(sampler2D(tFontAtlas, tFontSampler), In.UV.st).a; + float fSmoothWidth = fwidth(fDistance); + float fAlpha = smoothstep(0.5 - fSmoothWidth, 0.5 + fSmoothWidth, fDistance); + vec3 fRgbVec = In.Color.rgb * texture(sampler2D(tFontAtlas, tFontSampler), In.UV.st).rgb; + fColor = vec4(fRgbVec, fAlpha); +} \ No newline at end of file diff --git a/shaders/metal/draw_2d.metal b/shaders/metal/draw_2d.metal new file mode 100644 index 00000000..99ab09d1 --- /dev/null +++ b/shaders/metal/draw_2d.metal @@ -0,0 +1,62 @@ +#include +using namespace metal; + +//----------------------------------------------------------------------------- +// [SECTION] bind group 0 +//----------------------------------------------------------------------------- + +struct BindGroup_0 +{ + sampler tDefaultSampler; +}; + +//----------------------------------------------------------------------------- +// [SECTION] bind group 1 +//----------------------------------------------------------------------------- + +struct BindGroup_1 +{ + texture2d tFontAtlas; +}; + +//----------------------------------------------------------------------------- +// [SECTION] dynamic bind group +//----------------------------------------------------------------------------- + +struct DynamicData +{ + float4 data; +}; + +struct VertexIn { + float2 aPos [[attribute(0)]]; + float2 aUV [[attribute(1)]]; + uchar4 aColor [[attribute(2)]]; +}; + +struct VertexOut { + float4 position [[position]]; + float2 UV; + float4 Color; +}; + +vertex VertexOut vertex_main( + VertexIn in [[stage_in]], + device const DynamicData& tObjectInfo [[ buffer(3) ]]) +{ + VertexOut out; + out.Color = float4(in.aColor) / float4(255.0); + out.UV = in.aUV; + out.position = float4(in.aPos * tObjectInfo.data.xy + tObjectInfo.data.zw, 0, 1); + out.position.y = out.position.y * -1.0; + return out; +} + +fragment float4 fragment_main( + VertexOut in [[stage_in]], + device const BindGroup_0& bg0 [[ buffer(1) ]], + device const BindGroup_1& bg1 [[ buffer(2) ]]) +{ + float4 tTextureColor = bg1.tFontAtlas.sample(bg0.tDefaultSampler, in.UV); + return in.Color * tTextureColor; +} \ No newline at end of file diff --git a/shaders/metal/draw_2d_sdf.metal b/shaders/metal/draw_2d_sdf.metal new file mode 100644 index 00000000..fb63edd1 --- /dev/null +++ b/shaders/metal/draw_2d_sdf.metal @@ -0,0 +1,65 @@ +#include +using namespace metal; + +//----------------------------------------------------------------------------- +// [SECTION] bind group 0 +//----------------------------------------------------------------------------- + +struct BindGroup_0 +{ + sampler tDefaultSampler; +}; + +//----------------------------------------------------------------------------- +// [SECTION] bind group 1 +//----------------------------------------------------------------------------- + +struct BindGroup_1 +{ + texture2d tFontAtlas; +}; + +//----------------------------------------------------------------------------- +// [SECTION] dynamic bind group +//----------------------------------------------------------------------------- + +struct DynamicData +{ + float4 data; +}; + +struct VertexIn { + float2 aPos [[attribute(0)]]; + float2 aUV [[attribute(1)]]; + uchar4 aColor [[attribute(2)]]; +}; + +struct VertexOut { + float4 position [[position]]; + float2 UV; + float4 Color; +}; + +vertex VertexOut vertex_main( + VertexIn in [[stage_in]], + device const DynamicData& tObjectInfo [[ buffer(3) ]]) +{ + VertexOut out; + out.Color = float4(in.aColor) / float4(255.0); + out.UV = in.aUV; + out.position = float4(in.aPos * tObjectInfo.data.xy + tObjectInfo.data.zw, 0, 1); + out.position.y = out.position.y * -1.0; + return out; +} + +fragment float4 fragment_main( + VertexOut in [[stage_in]], + device const BindGroup_0& bg0 [[ buffer(1) ]], + device const BindGroup_1& bg1 [[ buffer(2) ]]) +{ + float distance = bg1.tFontAtlas.sample(bg0.tDefaultSampler, in.UV).a; + float smoothWidth = fwidth(distance); + float alpha = smoothstep(0.5 - smoothWidth, 0.5 + smoothWidth, distance); + float3 texColor = bg1.tFontAtlas.sample(bg0.tDefaultSampler, in.UV).rgb * float3(in.Color.rgb); + return float4(texColor, alpha); +} \ No newline at end of file diff --git a/shaders/metal/draw_3d.metal b/shaders/metal/draw_3d.metal index 921e223a..8adabb45 100644 --- a/shaders/metal/draw_3d.metal +++ b/shaders/metal/draw_3d.metal @@ -17,7 +17,7 @@ struct DynamicData struct VertexIn { float3 position [[attribute(0)]]; - float4 color [[attribute(1)]]; + uchar4 color [[attribute(1)]]; }; //----------------------------------------------------------------------------- @@ -36,15 +36,15 @@ vertex_main(VertexIn in [[stage_in]], device DynamicData& bg0 [[ buffer(1)]]) { VertexOut out; + out.color = float4(in.color) / float4(255.0); out.position = bg0.tMVP * float4(in.position, 1); out.position.y *= -1; - out.color = in.color; return out; } -fragment half4 +fragment float4 fragment_main(VertexOut in [[stage_in]], device DynamicData& bg0 [[ buffer(1)]]) { - return half4(in.color); + return in.color; } \ No newline at end of file diff --git a/shaders/metal/draw_3d_line.metal b/shaders/metal/draw_3d_line.metal index c39399d1..f430fb07 100644 --- a/shaders/metal/draw_3d_line.metal +++ b/shaders/metal/draw_3d_line.metal @@ -20,7 +20,7 @@ struct VertexIn float3 aPos [[attribute(0)]]; float4 aInfo [[attribute(1)]]; float3 aPosOther [[attribute(2)]]; - float4 color [[attribute(3)]]; + uchar4 color [[attribute(3)]]; }; //----------------------------------------------------------------------------- @@ -59,9 +59,9 @@ vertex_main(VertexIn in [[stage_in]], // offset by the direction of this point in the pair (-1 or 1) float4 offset = float4(normal * in.aInfo.x, 0.0, 0.0); VertexOut out; + out.color = float4(in.color) / float4(255.0); out.position = tCurrentProj + offset; out.position.y *= -1; - out.color = in.color; return out; } diff --git a/src/pilotlight.h b/src/pilotlight.h index 8dc41be2..724c0d52 100644 --- a/src/pilotlight.h +++ b/src/pilotlight.h @@ -37,6 +37,9 @@ typedef struct _plDataRegistryI plDataRegistryI; #define PL_API_EXTENSION_REGISTRY "PL_API_EXTENSION_REGISTRY" typedef struct _plExtensionRegistryI plExtensionRegistryI; +#define PL_API_IO "PL_API_IO" +typedef struct _plIOI plIOI; + //----------------------------------------------------------------------------- // [SECTION] contexts //----------------------------------------------------------------------------- @@ -50,6 +53,7 @@ typedef struct _plExtensionRegistryI plExtensionRegistryI; #include #include #include +#include "pl_math.h" //----------------------------------------------------------------------------- // [SECTION] forward declarations & basic types @@ -63,10 +67,26 @@ typedef struct _plMemoryContext plMemoryContext; typedef struct _plAllocationEntry plAllocationEntry; typedef struct _plDataObject plDataObject; typedef union _plDataID plDataID; +typedef struct _plIO plIO; // configuration & IO between app & pilotlight ui +typedef struct _plKeyData plKeyData; // individual key status (down, down duration, etc.) +typedef struct _plInputEvent plInputEvent; // holds data for input events (opaque structure) // external forward declarations typedef struct _plHashMap plHashMap; // pl_ds.h +// enums +typedef int plKey; // -> enum plKey_ // Enum: A key identifier (PL_KEY_XXX or PL_KEY_MOD_XXX value) +typedef int plMouseButton; // -> enum plMouseButton_ // Enum: A mouse button identifier (PL_MOUSE_BUTTON_XXX) +typedef int plMouseCursor; // -> enum plMouseCursor_ // Enum: Mouse cursor shape (PL_MOUSE_CURSOR_XXX) +typedef int plInputEventType; // -> enum plInputEventType_ // Enum: An input event type (PL_INPUT_EVENT_TYPE_XXX) +typedef int plInputEventSource; // -> enum plInputEventSource_ // Enum: An input event source (PL_INPUT_EVENT_SOURCE_XXX) + +// flags +typedef int plKeyChord; + +// character types +typedef uint16_t plUiWChar; + //----------------------------------------------------------------------------- // [SECTION] public api //----------------------------------------------------------------------------- @@ -131,6 +151,255 @@ typedef struct _plExtensionRegistryI void (*unload) (const char* pcName); } plExtensionRegistryI; +typedef struct _plIOI +{ + void (*new_frame)(void); + + plIO* (*get_io)(void); + + // keyboard + bool (*is_key_down) (plKey); + bool (*is_key_pressed) (plKey, bool bRepeat); + bool (*is_key_released) (plKey); + int (*get_key_pressed_amount)(plKey, float fRepeatDelay, float fRate); + + // mouse + bool (*is_mouse_down) (plMouseButton); + bool (*is_mouse_clicked) (plMouseButton, bool bRepeat); + bool (*is_mouse_released) (plMouseButton); + bool (*is_mouse_double_clicked)(plMouseButton); + bool (*is_mouse_dragging) (plMouseButton, float fThreshold); + bool (*is_mouse_hovering_rect) (plVec2 minVec, plVec2 maxVec); + void (*reset_mouse_drag_delta) (plMouseButton); + plVec2 (*get_mouse_drag_delta) (plMouseButton, float fThreshold); + plVec2 (*get_mouse_pos) (void); + float (*get_mouse_wheel) (void); + bool (*is_mouse_pos_valid) (plVec2); + void (*set_mouse_cursor) (plMouseCursor); + + // input functions + plKeyData* (*get_key_data) (plKey); + void (*add_key_event) (plKey, bool bDown); + void (*add_text_event) (uint32_t uChar); + void (*add_text_event_utf16) (uint16_t uChar); + void (*add_text_events_utf8) (const char* pcText); + void (*add_mouse_pos_event) (float fX, float fY); + void (*add_mouse_button_event)(int iButton, bool bDown); + void (*add_mouse_wheel_event) (float fX, float fY); + void (*clear_input_characters)(void); +} plIOI; + +//----------------------------------------------------------------------------- +// [SECTION] enums +//----------------------------------------------------------------------------- + +enum plMouseButton_ +{ + PL_MOUSE_BUTTON_LEFT = 0, + PL_MOUSE_BUTTON_RIGHT = 1, + PL_MOUSE_BUTTON_MIDDLE = 2, + PL_MOUSE_BUTTON_COUNT = 5 +}; + +enum plKey_ +{ + PL_KEY_NONE = 0, + PL_KEY_TAB, + PL_KEY_LEFT_ARROW, + PL_KEY_RIGHT_ARROW, + PL_KEY_UP_ARROW, + PL_KEY_DOWN_ARROW, + PL_KEY_PAGE_UP, + PL_KEY_PAGE_DOWN, + PL_KEY_HOME, + PL_KEY_END, + PL_KEY_INSERT, + PL_KEY_DELETE, + PL_KEY_BACKSPACE, + PL_KEY_SPACE, + PL_KEY_ENTER, + PL_KEY_ESCAPE, + PL_KEY_LEFT_CTRL, + PL_KEY_LEFT_SHIFT, + PL_KEY_LEFT_ALT, + PL_KEY_LEFT_SUPER, + PL_KEY_RIGHT_CTRL, + PL_KEY_RIGHT_SHIFT, + PL_KEY_RIGHT_ALT, + PL_KEY_RIGHT_SUPER, + PL_KEY_MENU, + PL_KEY_0, PL_KEY_1, PL_KEY_2, PL_KEY_3, PL_KEY_4, PL_KEY_5, PL_KEY_6, PL_KEY_7, PL_KEY_8, PL_KEY_9, + PL_KEY_A, PL_KEY_B, PL_KEY_C, PL_KEY_D, PL_KEY_E, PL_KEY_F, PL_KEY_G, PL_KEY_H, PL_KEY_I, PL_KEY_J, + PL_KEY_K, PL_KEY_L, PL_KEY_M, PL_KEY_N, PL_KEY_O, PL_KEY_P, PL_KEY_Q, PL_KEY_R, PL_KEY_S, PL_KEY_T, + PL_KEY_U, PL_KEY_V, PL_KEY_W, PL_KEY_X, PL_KEY_Y, PL_KEY_Z, + PL_KEY_F1, PL_KEY_F2, PL_KEY_F3,PL_KEY_F4, PL_KEY_F5, PL_KEY_F6, PL_KEY_F7, PL_KEY_F8, PL_KEY_F9, + PL_KEY_F10, PL_KEY_F11, PL_KEY_F12, PL_KEY_F13, PL_KEY_F14, PL_KEY_F15, PL_KEY_F16, PL_KEY_F17, + PL_KEY_F18, PL_KEY_F19, PL_KEY_F20, PL_KEY_F21, PL_KEY_F22, PL_KEY_F23, PL_KEY_F24, + PL_KEY_APOSTROPHE, // ' + PL_KEY_COMMA, // , + PL_KEY_MINUS, // - + PL_KEY_PERIOD, // . + PL_KEY_SLASH, // / + PL_KEY_SEMICOLON, // ; + PL_KEY_EQUAL, // = + PL_KEY_LEFT_BRACKET, // [ + PL_KEY_BACKSLASH, // \ (this text inhibit multiline comment caused by backslash) + PL_KEY_RIGHT_BRACKET, // ] + PL_KEY_GRAVE_ACCENT, // ` + PL_KEY_CAPS_LOCK, + PL_KEY_SCROLL_LOCK, + PL_KEY_NUM_LOCK, + PL_KEY_PRINT_SCREEN, + PL_KEY_PAUSE, + PL_KEY_KEYPAD_0, PL_KEY_KEYPAD_1, PL_KEY_KEYPAD_2, PL_KEY_KEYPAD_3, + PL_KEY_KEYPAD_4, PL_KEY_KEYPAD_5, PL_KEY_KEYPAD_6, PL_KEY_KEYPAD_7, + PL_KEY_KEYPAD_8, PL_KEY_KEYPAD_9, + PL_KEY_KEYPAD_DECIMAL, + PL_KEY_KEYPAD_DIVIDE, + PL_KEY_KEYPAD_MULTIPLY, + PL_KEY_KEYPAD_SUBTRACT, + PL_KEY_KEYPAD_ADD, + PL_KEY_KEYPAD_ENTER, + PL_KEY_KEYPAD_EQUAL, + + PL_KEY_RESERVED_MOD_CTRL, PL_KEY_RESERVED_MOD_SHIFT, PL_KEY_RESERVED_MOD_ALT, PL_RESERVED_KEY_MOD_SUPER, + PL_KEY_COUNT, // no valid plKey_ is ever greater than this value + + PL_KEY_MOD_NONE = 0, + PL_KEY_MOD_CTRL = 1 << 12, // ctrl + PL_KEY_MOD_SHIFT = 1 << 13, // shift + PL_KEY_MOD_ALT = 1 << 14, // option/menu + PL_KEY_MOD_SUPER = 1 << 15, // cmd/super/windows + PL_KEY_MOD_SHORTCUT = 1 << 11, // alias for Ctrl (non-macOS) _or_ super (macOS) + PL_KEY_MOD_MASK_ = 0xF800 // 5 bits +}; + +enum plMouseCursor_ +{ + PL_MOUSE_CURSOR_NONE = -1, + PL_MOUSE_CURSOR_ARROW = 0, + PL_MOUSE_CURSOR_TEXT_INPUT, + PL_MOUSE_CURSOR_RESIZE_ALL, + PL_MOUSE_CURSOR_RESIZE_NS, + PL_MOUSE_CURSOR_RESIZE_EW, + PL_MOUSE_CURSOR_RESIZE_NESW, + PL_MOUSE_CURSOR_RESIZE_NWSE, + PL_MOUSE_CURSOR_HAND, + PL_MOUSE_CURSOR_NOT_ALLOWED, + PL_MOUSE_CURSOR_COUNT +}; + +//----------------------------------------------------------------------------- +// [SECTION] IO struct +//----------------------------------------------------------------------------- + +typedef struct _plKeyData +{ + bool bDown; + float fDownDuration; + float fDownDurationPrev; +} plKeyData; + +typedef struct _plIO +{ + + //------------------------------------------------------------------ + // Configuration + //------------------------------------------------------------------ + + float fDeltaTime; + float fMouseDragThreshold; // default 6.0f + float fMouseDoubleClickTime; // default 0.3f seconds + float fMouseDoubleClickMaxDist; // default 6.0f + float fKeyRepeatDelay; // default 0.275f + float fKeyRepeatRate; // default 0.050f + float afMainViewportSize[2]; + float afMainFramebufferScale[2]; + void* pUserData; + + // miscellaneous options + bool bConfigMacOSXBehaviors; + + //------------------------------------------------------------------ + // platform functions + //------------------------------------------------------------------ + + void* pBackendPlatformData; + void* pBackendRendererData; + + // access OS clipboard + // (default to use native Win32 clipboard on Windows, otherwise uses a private clipboard. Override to access OS clipboard on other architectures) + const char* (*get_clipboard_text_fn)(void* pUserData); + void (*set_clipboard_text_fn)(void* pUserData, const char* pcText); + void* pClipboardUserData; + char* sbcClipboardData; + + //------------------------------------------------------------------ + // Input/Output + //------------------------------------------------------------------ + + bool bRunning; + + //------------------------------------------------------------------ + // Output + //------------------------------------------------------------------ + + double dTime; + float fFrameRate; // rough estimate(rolling average of fDeltaTime over 120 frames) + bool bViewportSizeChanged; + bool bViewportMinimized; + uint64_t ulFrameCount; + + plKeyChord tKeyMods; + bool bWantCaptureMouse; + bool bWantCaptureKeyboard; + bool bWantTextInput; + bool bKeyCtrl; // Keyboard modifier down: Control + bool bKeyShift; // Keyboard modifier down: Shift + bool bKeyAlt; // Keyboard modifier down: Alt + bool bKeySuper; // Keyboard modifier down: Cmd/Super/Windows + + // [INTERNAL] + plInputEvent* _sbtInputEvents; + plUiWChar* _sbInputQueueCharacters; + plUiWChar _tInputQueueSurrogate; + + // main input state + plVec2 _tMousePos; + bool _abMouseDown[5]; + int _iMouseButtonsDown; + float _fMouseWheel; + float _fMouseWheelH; + + // mouse cursor + plMouseCursor tCurrentCursor; + plMouseCursor tNextCursor; + bool bCursorChanged; + + // other state + plKeyData _tKeyData[PL_KEY_COUNT]; + plVec2 _tLastValidMousePos; + plVec2 _tMouseDelta; + plVec2 _tMousePosPrev; // previous mouse position + plVec2 _atMouseClickedPos[5]; // position when clicked + double _adMouseClickedTime[5]; // time of last click + bool _abMouseClicked[5]; // mouse went from !down to down + bool _abMouseOwned[5]; + uint32_t _auMouseClickedCount[5]; // + uint32_t _auMouseClickedLastCount[5]; // + bool _abMouseReleased[5]; // mouse went from down to !down + float _afMouseDownDuration[5]; // duration mouse button has been down (0.0f == just clicked) + float _afMouseDownDurationPrev[5]; // previous duration of mouse button down + float _afMouseDragMaxDistSqr[5]; // squared max distance mouse traveled from clicked position + + // frame rate calcs + float _afFrameRateSecPerFrame[120]; + int _iFrameRateSecPerFrameIdx; + int _iFrameRateSecPerFrameCount; + float _fFrameRateSecPerFrameAccum; + +} plIO; + //----------------------------------------------------------------------------- // [SECTION] structs (not for public use, subject to change) //----------------------------------------------------------------------------- diff --git a/src/pilotlight_exe.c b/src/pilotlight_exe.c index 12004e3f..66bef764 100644 --- a/src/pilotlight_exe.c +++ b/src/pilotlight_exe.c @@ -16,6 +16,7 @@ Index of this file: // [SECTION] includes //----------------------------------------------------------------------------- +#include // FLT_MAX #include // bool #include // strcmp #include "pilotlight.h" @@ -23,6 +24,15 @@ Index of this file: #include "pl_ds.h" #include "pl_memory.h" #include "pl_os.h" +#include "pl_string.h" +#define PL_MATH_INCLUDE_FUNCTIONS +#include "pl_math.h" + +//----------------------------------------------------------------------------- +// [SECTION] defines +//----------------------------------------------------------------------------- + +#define PL_VEC2_LENGTH_SQR(vec) (((vec).x * (vec).x) + ((vec).y * (vec).y)) //----------------------------------------------------------------------------- // [SECTION] internal structs @@ -71,6 +81,71 @@ typedef struct _plDataObject plDataObjectProperty* ptProperties; } plDataObject; +typedef struct _plInputEvent +{ + plInputEventType tType; + plInputEventSource tSource; + + union + { + struct // mouse pos event + { + float fPosX; + float fPosY; + }; + + struct // mouse wheel event + { + float fWheelX; + float fWheelY; + }; + + struct // mouse button event + { + int iButton; + bool bMouseDown; + }; + + struct // key event + { + plKey tKey; + bool bKeyDown; + }; + + struct // text event + { + uint32_t uChar; + }; + + }; + +} plInputEvent; + +//----------------------------------------------------------------------------- +// [SECTION] enums +//----------------------------------------------------------------------------- + +typedef enum +{ + PL_INPUT_EVENT_TYPE_NONE = 0, + PL_INPUT_EVENT_TYPE_MOUSE_POS, + PL_INPUT_EVENT_TYPE_MOUSE_WHEEL, + PL_INPUT_EVENT_TYPE_MOUSE_BUTTON, + PL_INPUT_EVENT_TYPE_KEY, + PL_INPUT_EVENT_TYPE_TEXT, + + PL_INPUT_EVENT_TYPE_COUNT +} _plInputEventType; + +typedef enum +{ + PL_INPUT_EVENT_SOURCE_NONE = 0, + PL_INPUT_EVENT_SOURCE_MOUSE, + PL_INPUT_EVENT_SOURCE_KEYBOARD, + + PL_INPUT_EVENT_SOURCE_COUNT +} _plInputEventSource; + //----------------------------------------------------------------------------- // [SECTION] internal api //----------------------------------------------------------------------------- @@ -110,6 +185,13 @@ static void pl__handle_extension_reloads (void); // extension registry helper functions static void pl__create_extension(const char* pcName, const char* pcLoadFunc, const char* pcUnloadFunc, plExtension* ptExtensionOut); +// IO helper functions +static void pl__update_events(void); +static void pl__update_mouse_inputs(void); +static void pl__update_keyboard_inputs(void); +static int pl__calc_typematic_repeat_amount(float fT0, float fT1, float fRepeatDelay, float fRepeatRate); +static plInputEvent* pl__get_last_event(plInputEventType tType, int iButtonOrKey); + static const plApiRegistryI* pl__load_api_registry(void) { @@ -142,69 +224,20 @@ plExtension* gsbtExtensions = NULL; plSharedLibrary** gsbptLibs = NULL; uint32_t* gsbtHotLibs = NULL; -//----------------------------------------------------------------------------- -// [SECTION] public api implementation -//----------------------------------------------------------------------------- - -const plApiRegistryI* -pl_load_core_apis(void) -{ - - const plApiRegistryI* ptApiRegistry = pl__load_api_registry(); - pl__create_mutex(&gptDataMutex); - - pl_sb_resize(gtDataRegistryData.sbtFreeDataIDs, 1024); - for(uint32_t i = 0; i < 1024; i++) - { - gtDataRegistryData.sbtFreeDataIDs[i].uIndex = i; - } - - static const plDataRegistryI tDataRegistryApi = { - .set_data = pl__set_data, - .get_data = pl__get_data, - .garbage_collect = pl__garbage_collect, - .create_object = pl__create_object, - .get_object_by_name = pl__get_object_by_name, - .read = pl__read, - .end_read = pl__end_read, - .get_string = pl__get_string, - .get_buffer = pl__get_buffer, - .write = pl__write, - .set_string = pl__set_string, - .set_buffer = pl__set_buffer, - .commit = pl__commit - }; - - static const plExtensionRegistryI tExtensionRegistryApi = { - .load = pl__load_extension, - .unload = pl__unload_extension, - .unload_all = pl__unload_all_extensions, - .reload = pl__handle_extension_reloads - }; - - // apis more likely to not be stored, should be first (api registry is not sorted) - ptApiRegistry->add(PL_API_DATA_REGISTRY, &tDataRegistryApi); - ptApiRegistry->add(PL_API_EXTENSION_REGISTRY, &tExtensionRegistryApi); - - return ptApiRegistry; -} - -void -pl_unload_core_apis(void) -{ - const uint32_t uApiCount = pl_sb_size(gsbApiEntries); - for(uint32_t i = 0; i < uApiCount; i++) - { - pl_sb_free(gsbApiEntries[i].sbSubscribers); - pl_sb_free(gsbApiEntries[i].sbUserData); - } - - pl_sb_free(gsbtExtensions); - pl_sb_free(gsbptLibs); - pl_sb_free(gsbtHotLibs); - pl_sb_free(gsbApiEntries); - pl_hm_free(>HashMap); -} +// IO +plIO gtIO = { + .fMouseDoubleClickTime = 0.3f, + .fMouseDoubleClickMaxDist = 6.0f, + .fMouseDragThreshold = 6.0f, + .fKeyRepeatDelay = 0.275f, + .fKeyRepeatRate = 0.050f, + .afMainFramebufferScale = {1.0f, 1.0f}, + .tCurrentCursor = PL_MOUSE_CURSOR_ARROW, + .tNextCursor = PL_MOUSE_CURSOR_ARROW, + .afMainViewportSize = {500.0f, 500.0f}, + .bViewportSizeChanged = true, + .bRunning = true, +}; //----------------------------------------------------------------------------- // [SECTION] internal api implementation @@ -605,6 +638,617 @@ pl__handle_extension_reloads(void) } } +plKeyData* +pl_get_key_data(plKey tKey) +{ + if(tKey & PL_KEY_MOD_MASK_) + { + if (tKey == PL_KEY_MOD_CTRL) tKey = PL_KEY_RESERVED_MOD_CTRL; + else if(tKey == PL_KEY_MOD_SHIFT) tKey = PL_KEY_RESERVED_MOD_SHIFT; + else if(tKey == PL_KEY_MOD_ALT) tKey = PL_KEY_RESERVED_MOD_ALT; + else if(tKey == PL_KEY_MOD_SUPER) tKey = PL_RESERVED_KEY_MOD_SUPER; + else if(tKey == PL_KEY_MOD_SHORTCUT) tKey = (gtIO.bConfigMacOSXBehaviors ? PL_RESERVED_KEY_MOD_SUPER : PL_KEY_RESERVED_MOD_CTRL); + } + assert(tKey > PL_KEY_NONE && tKey < PL_KEY_COUNT && "Key not valid"); + return >IO._tKeyData[tKey]; +} + +void +pl_add_key_event(plKey tKey, bool bDown) +{ + // check for duplicate + const plInputEvent* ptLastEvent = pl__get_last_event(PL_INPUT_EVENT_TYPE_KEY, (int)tKey); + if(ptLastEvent && ptLastEvent->bKeyDown == bDown) + return; + + const plInputEvent tEvent = { + .tType = PL_INPUT_EVENT_TYPE_KEY, + .tSource = PL_INPUT_EVENT_SOURCE_KEYBOARD, + .tKey = tKey, + .bKeyDown = bDown + }; + pl_sb_push(gtIO._sbtInputEvents, tEvent); +} + +void +pl_add_text_event(uint32_t uChar) +{ + const plInputEvent tEvent = { + .tType = PL_INPUT_EVENT_TYPE_TEXT, + .tSource = PL_INPUT_EVENT_SOURCE_KEYBOARD, + .uChar = uChar + }; + pl_sb_push(gtIO._sbtInputEvents, tEvent); +} + +void +pl_add_text_event_utf16(uint16_t uChar) +{ + if (uChar == 0 && gtIO._tInputQueueSurrogate == 0) + return; + + if ((uChar & 0xFC00) == 0xD800) // High surrogate, must save + { + if (gtIO._tInputQueueSurrogate != 0) + pl_add_text_event(0xFFFD); + gtIO._tInputQueueSurrogate = uChar; + return; + } + + plUiWChar cp = uChar; + if (gtIO._tInputQueueSurrogate != 0) + { + if ((uChar & 0xFC00) != 0xDC00) // Invalid low surrogate + { + pl_add_text_event(0xFFFD); + } + else + { + cp = 0xFFFD; // Codepoint will not fit in ImWchar + } + + gtIO._tInputQueueSurrogate = 0; + } + pl_add_text_event((uint32_t)cp); +} + +void +pl_add_text_events_utf8(const char* pcText) +{ + while(*pcText != 0) + { + uint32_t uChar = 0; + pcText += pl_text_char_from_utf8(&uChar, pcText, NULL); + pl_add_text_event(uChar); + } +} + +void +pl_add_mouse_pos_event(float fX, float fY) +{ + + // check for duplicate + const plInputEvent* ptLastEvent = pl__get_last_event(PL_INPUT_EVENT_TYPE_MOUSE_POS, (int)(fX + fY)); + if(ptLastEvent && ptLastEvent->fPosX == fX && ptLastEvent->fPosY == fY) + return; + + const plInputEvent tEvent = { + .tType = PL_INPUT_EVENT_TYPE_MOUSE_POS, + .tSource = PL_INPUT_EVENT_SOURCE_MOUSE, + .fPosX = fX, + .fPosY = fY + }; + pl_sb_push(gtIO._sbtInputEvents, tEvent); +} + +void +pl_add_mouse_button_event(int iButton, bool bDown) +{ + + // check for duplicate + const plInputEvent* ptLastEvent = pl__get_last_event(PL_INPUT_EVENT_TYPE_MOUSE_BUTTON, iButton); + if(ptLastEvent && ptLastEvent->bMouseDown == bDown) + return; + + const plInputEvent tEvent = { + .tType = PL_INPUT_EVENT_TYPE_MOUSE_BUTTON, + .tSource = PL_INPUT_EVENT_SOURCE_MOUSE, + .iButton = iButton, + .bMouseDown = bDown + }; + pl_sb_push(gtIO._sbtInputEvents, tEvent); +} + +void +pl_add_mouse_wheel_event(float fX, float fY) +{ + + const plInputEvent tEvent = { + .tType = PL_INPUT_EVENT_TYPE_MOUSE_WHEEL, + .tSource = PL_INPUT_EVENT_SOURCE_MOUSE, + .fWheelX = fX, + .fWheelY = fY + }; + pl_sb_push(gtIO._sbtInputEvents, tEvent); +} + +void +pl_clear_input_characters(void) +{ + pl_sb_reset(gtIO._sbInputQueueCharacters); +} + +bool +pl_is_key_down(plKey tKey) +{ + const plKeyData* ptData = pl_get_key_data(tKey); + return ptData->bDown; +} + +int +pl_get_key_pressed_amount(plKey tKey, float fRepeatDelay, float fRate) +{ + const plKeyData* ptData = pl_get_key_data(tKey); + if (!ptData->bDown) // In theory this should already be encoded as (DownDuration < 0.0f), but testing this facilitates eating mechanism (until we finish work on input ownership) + return 0; + const float fT = ptData->fDownDuration; + return pl__calc_typematic_repeat_amount(fT - gtIO.fDeltaTime, fT, fRepeatDelay, fRate); +} + +bool +pl_is_key_pressed(plKey tKey, bool bRepeat) +{ + const plKeyData* ptData = pl_get_key_data(tKey); + if (!ptData->bDown) // In theory this should already be encoded as (DownDuration < 0.0f), but testing this facilitates eating mechanism (until we finish work on input ownership) + return false; + const float fT = ptData->fDownDuration; + if (fT < 0.0f) + return false; + + bool bPressed = (fT == 0.0f); + if (!bPressed && bRepeat) + { + const float fRepeatDelay = gtIO.fKeyRepeatDelay; + const float fRepeatRate = gtIO.fKeyRepeatRate; + bPressed = (fT > fRepeatDelay) && pl_get_key_pressed_amount(tKey, fRepeatDelay, fRepeatRate) > 0; + } + + if (!bPressed) + return false; + return true; +} + +bool +pl_is_key_released(plKey tKey) +{ + const plKeyData* ptData = pl_get_key_data(tKey); + if (ptData->fDownDurationPrev < 0.0f || ptData->bDown) + return false; + return true; +} + +bool +pl_is_mouse_down(plMouseButton tButton) +{ + return gtIO._abMouseDown[tButton]; +} + +bool +pl_is_mouse_clicked(plMouseButton tButton, bool bRepeat) +{ + if(!gtIO._abMouseDown[tButton]) + return false; + const float fT = gtIO._afMouseDownDuration[tButton]; + if(fT == 0.0f) + return true; + if(bRepeat && fT > gtIO.fKeyRepeatDelay) + return pl__calc_typematic_repeat_amount(fT - gtIO.fDeltaTime, fT, gtIO.fKeyRepeatDelay, gtIO.fKeyRepeatRate) > 0; + return false; +} + +bool +pl_is_mouse_released(plMouseButton tButton) +{ + return gtIO._abMouseReleased[tButton]; +} + +bool +pl_is_mouse_double_clicked(plMouseButton tButton) +{ + return gtIO._auMouseClickedCount[tButton] == 2; +} + +bool +pl_is_mouse_dragging(plMouseButton tButton, float fThreshold) +{ + if(!gtIO._abMouseDown[tButton]) + return false; + if(fThreshold < 0.0f) + fThreshold = gtIO.fMouseDragThreshold; + return gtIO._afMouseDragMaxDistSqr[tButton] >= fThreshold * fThreshold; +} + +bool +pl_is_mouse_hovering_rect(plVec2 minVec, plVec2 maxVec) +{ + const plVec2 tMousePos = gtIO._tMousePos; + return ( tMousePos.x >= minVec.x && tMousePos.y >= minVec.y && tMousePos.x <= maxVec.x && tMousePos.y <= maxVec.y); +} + +void +pl_reset_mouse_drag_delta(plMouseButton tButton) +{ + gtIO._atMouseClickedPos[tButton] = gtIO._tMousePos; +} + +plVec2 +pl_get_mouse_pos(void) +{ + return gtIO._tMousePos; +} + +float +pl_get_mouse_wheel(void) +{ + return gtIO._fMouseWheel; +} + +bool +pl_is_mouse_pos_valid(plVec2 tPos) +{ + return tPos.x > -FLT_MAX && tPos.y > -FLT_MAX; +} + +void +pl_set_mouse_cursor(plMouseCursor tCursor) +{ + gtIO.tNextCursor = tCursor; + gtIO.bCursorChanged = true; +} + +plVec2 +pl_get_mouse_drag_delta(plMouseButton tButton, float fThreshold) +{ + if(fThreshold < 0.0f) + fThreshold = gtIO.fMouseDragThreshold; + if(gtIO._abMouseDown[tButton] || gtIO._abMouseReleased[tButton]) + { + if(gtIO._afMouseDragMaxDistSqr[tButton] >= fThreshold * fThreshold) + { + if(pl_is_mouse_pos_valid(gtIO._tMousePos) && pl_is_mouse_pos_valid(gtIO._atMouseClickedPos[tButton])) + return pl_sub_vec2(gtIO._tLastValidMousePos, gtIO._atMouseClickedPos[tButton]); + } + } + + return pl_create_vec2(0.0f, 0.0f); +} + +plIO* +pl_get_io(void) +{ + return >IO; +} + +static void +pl__update_events(void) +{ + const uint32_t uEventCount = pl_sb_size(gtIO._sbtInputEvents); + for(uint32_t i = 0; i < uEventCount; i++) + { + plInputEvent* ptEvent = >IO._sbtInputEvents[i]; + + switch(ptEvent->tType) + { + case PL_INPUT_EVENT_TYPE_MOUSE_POS: + { + // PL_UI_DEBUG_LOG_IO("[%Iu] IO Mouse Pos (%0.0f, %0.0f)", gptCtx->frameCount, ptEvent->fPosX, ptEvent->fPosY); + + if(ptEvent->fPosX != -FLT_MAX && ptEvent->fPosY != -FLT_MAX) + { + gtIO._tMousePos.x = ptEvent->fPosX; + gtIO._tMousePos.y = ptEvent->fPosY; + } + break; + } + + case PL_INPUT_EVENT_TYPE_MOUSE_WHEEL: + { + // PL_UI_DEBUG_LOG_IO("[%Iu] IO Mouse Wheel (%0.0f, %0.0f)", gptCtx->frameCount, ptEvent->fWheelX, ptEvent->fWheelY); + gtIO._fMouseWheelH += ptEvent->fWheelX; + gtIO._fMouseWheel += ptEvent->fWheelY; + break; + } + + case PL_INPUT_EVENT_TYPE_MOUSE_BUTTON: + { + // PL_UI_DEBUG_LOG_IO(ptEvent->bMouseDown ? "[%Iu] IO Mouse Button %i down" : "[%Iu] IO Mouse Button %i up", gptCtx->frameCount, ptEvent->iButton); + assert(ptEvent->iButton >= 0 && ptEvent->iButton < PL_MOUSE_BUTTON_COUNT); + gtIO._abMouseDown[ptEvent->iButton] = ptEvent->bMouseDown; + break; + } + + case PL_INPUT_EVENT_TYPE_KEY: + { + // if(ptEvent->tKey < PL_KEY_COUNT) + // PL_UI_DEBUG_LOG_IO(ptEvent->bKeyDown ? "[%Iu] IO Key %i down" : "[%Iu] IO Key %i up", gptCtx->frameCount, ptEvent->tKey); + plKey tKey = ptEvent->tKey; + assert(tKey != PL_KEY_NONE); + plKeyData* ptKeyData = pl_get_key_data(tKey); + ptKeyData->bDown = ptEvent->bKeyDown; + break; + } + + case PL_INPUT_EVENT_TYPE_TEXT: + { + // PL_UI_DEBUG_LOG_IO("[%Iu] IO Text (U+%08u)", gptCtx->frameCount, (uint32_t)ptEvent->uChar); + plUiWChar uChar = (plUiWChar)ptEvent->uChar; + pl_sb_push(gtIO._sbInputQueueCharacters, uChar); + break; + } + + default: + { + assert(false && "unknown input event type"); + break; + } + } + } + pl_sb_reset(gtIO._sbtInputEvents) +} + +static void +pl__update_keyboard_inputs(void) +{ + gtIO.tKeyMods = 0; + if (pl_is_key_down(PL_KEY_LEFT_CTRL) || pl_is_key_down(PL_KEY_RIGHT_CTRL)) { gtIO.tKeyMods |= PL_KEY_MOD_CTRL; } + if (pl_is_key_down(PL_KEY_LEFT_SHIFT) || pl_is_key_down(PL_KEY_RIGHT_SHIFT)) { gtIO.tKeyMods |= PL_KEY_MOD_SHIFT; } + if (pl_is_key_down(PL_KEY_LEFT_ALT) || pl_is_key_down(PL_KEY_RIGHT_ALT)) { gtIO.tKeyMods |= PL_KEY_MOD_ALT; } + if (pl_is_key_down(PL_KEY_LEFT_SUPER) || pl_is_key_down(PL_KEY_RIGHT_SUPER)) { gtIO.tKeyMods |= PL_KEY_MOD_SUPER; } + + gtIO.bKeyCtrl = (gtIO.tKeyMods & PL_KEY_MOD_CTRL) != 0; + gtIO.bKeyShift = (gtIO.tKeyMods & PL_KEY_MOD_SHIFT) != 0; + gtIO.bKeyAlt = (gtIO.tKeyMods & PL_KEY_MOD_ALT) != 0; + gtIO.bKeySuper = (gtIO.tKeyMods & PL_KEY_MOD_SUPER) != 0; + + // Update keys + for (uint32_t i = 0; i < PL_KEY_COUNT; i++) + { + plKeyData* ptKeyData = >IO._tKeyData[i]; + ptKeyData->fDownDurationPrev = ptKeyData->fDownDuration; + ptKeyData->fDownDuration = ptKeyData->bDown ? (ptKeyData->fDownDuration < 0.0f ? 0.0f : ptKeyData->fDownDuration + gtIO.fDeltaTime) : -1.0f; + } +} + +static void +pl__update_mouse_inputs(void) +{ + if(pl_is_mouse_pos_valid(gtIO._tMousePos)) + { + gtIO._tMousePos.x = floorf(gtIO._tMousePos.x); + gtIO._tMousePos.y = floorf(gtIO._tMousePos.y); + gtIO._tLastValidMousePos = gtIO._tMousePos; + } + + // only calculate data if the current & previous mouse position are valid + if(pl_is_mouse_pos_valid(gtIO._tMousePos) && pl_is_mouse_pos_valid(gtIO._tMousePosPrev)) + gtIO._tMouseDelta = pl_sub_vec2(gtIO._tMousePos, gtIO._tMousePosPrev); + else + { + gtIO._tMouseDelta.x = 0.0f; + gtIO._tMouseDelta.y = 0.0f; + } + gtIO._tMousePosPrev = gtIO._tMousePos; + + for(uint32_t i = 0; i < PL_MOUSE_BUTTON_COUNT; i++) + { + gtIO._abMouseClicked[i] = gtIO._abMouseDown[i] && gtIO._afMouseDownDuration[i] < 0.0f; + gtIO._auMouseClickedCount[i] = 0; + gtIO._abMouseReleased[i] = !gtIO._abMouseDown[i] && gtIO._afMouseDownDuration[i] >= 0.0f; + gtIO._afMouseDownDurationPrev[i] = gtIO._afMouseDownDuration[i]; + gtIO._afMouseDownDuration[i] = gtIO._abMouseDown[i] ? (gtIO._afMouseDownDuration[i] < 0.0f ? 0.0f : gtIO._afMouseDownDuration[i] + gtIO.fDeltaTime) : -1.0f; + + if(gtIO._abMouseClicked[i]) + { + + bool bIsRepeatedClick = false; + if((float)(gtIO.dTime - gtIO._adMouseClickedTime[i]) < gtIO.fMouseDoubleClickTime) + { + plVec2 tDeltaFromClickPos = pl_create_vec2(0.0f, 0.0f); + if(pl_is_mouse_pos_valid(gtIO._tMousePos)) + tDeltaFromClickPos = pl_sub_vec2(gtIO._tMousePos, gtIO._atMouseClickedPos[i]); + + if(PL_VEC2_LENGTH_SQR(tDeltaFromClickPos) < gtIO.fMouseDoubleClickMaxDist * gtIO.fMouseDoubleClickMaxDist) + bIsRepeatedClick = true; + } + + if(bIsRepeatedClick) + gtIO._auMouseClickedLastCount[i]++; + else + gtIO._auMouseClickedLastCount[i] = 1; + + gtIO._adMouseClickedTime[i] = gtIO.dTime; + gtIO._atMouseClickedPos[i] = gtIO._tMousePos; + gtIO._afMouseDragMaxDistSqr[i] = 0.0f; + gtIO._auMouseClickedCount[i] = gtIO._auMouseClickedLastCount[i]; + } + else if(gtIO._abMouseDown[i]) + { + const plVec2 tClickPos = pl_sub_vec2(gtIO._tLastValidMousePos, gtIO._atMouseClickedPos[i]); + float fDeltaSqrClickPos = PL_VEC2_LENGTH_SQR(tClickPos); + gtIO._afMouseDragMaxDistSqr[i] = pl_max(fDeltaSqrClickPos, gtIO._afMouseDragMaxDistSqr[i]); + } + } +} + +static int +pl__calc_typematic_repeat_amount(float fT0, float fT1, float fRepeatDelay, float fRepeatRate) +{ + if(fT1 == 0.0f) + return 1; + if(fT0 >= fT1) + return 0; + if(fRepeatRate <= 0.0f) + return (fT0 < fRepeatDelay) && (fT1 >= fRepeatDelay); + + const int iCountT0 = (fT0 < fRepeatDelay) ? -1 : (int)((fT0 - fRepeatDelay) / fRepeatRate); + const int iCountT1 = (fT1 < fRepeatDelay) ? -1 : (int)((fT1 - fRepeatDelay) / fRepeatRate); + const int iCount = iCountT1 - iCountT0; + return iCount; +} + +static plInputEvent* +pl__get_last_event(plInputEventType tType, int iButtonOrKey) +{ + const uint32_t uEventCount = pl_sb_size(gtIO._sbtInputEvents); + for(uint32_t i = 0; i < uEventCount; i++) + { + plInputEvent* ptEvent = >IO._sbtInputEvents[uEventCount - i - 1]; + if(ptEvent->tType != tType) + continue; + if(tType == PL_INPUT_EVENT_TYPE_KEY && (int)ptEvent->tKey != iButtonOrKey) + continue; + else if(tType == PL_INPUT_EVENT_TYPE_MOUSE_BUTTON && ptEvent->iButton != iButtonOrKey) + continue; + else if(tType == PL_INPUT_EVENT_TYPE_MOUSE_POS && (int)(ptEvent->fPosX + ptEvent->fPosY) != iButtonOrKey) + continue; + return ptEvent; + } + return NULL; +} + +void +pl_new_frame(void) +{ + + // update IO structure + gtIO.dTime += (double)gtIO.fDeltaTime; + gtIO.ulFrameCount++; + gtIO.bViewportSizeChanged = false; + + // calculate frame rate + gtIO._fFrameRateSecPerFrameAccum += gtIO.fDeltaTime - gtIO._afFrameRateSecPerFrame[gtIO._iFrameRateSecPerFrameIdx]; + gtIO._afFrameRateSecPerFrame[gtIO._iFrameRateSecPerFrameIdx] = gtIO.fDeltaTime; + gtIO._iFrameRateSecPerFrameIdx = (gtIO._iFrameRateSecPerFrameIdx + 1) % 120; + gtIO._iFrameRateSecPerFrameCount = pl_max(gtIO._iFrameRateSecPerFrameCount, 120); + gtIO.fFrameRate = FLT_MAX; + if(gtIO._fFrameRateSecPerFrameAccum > 0) + gtIO.fFrameRate = ((float) gtIO._iFrameRateSecPerFrameCount) / gtIO._fFrameRateSecPerFrameAccum; + + // handle events + pl__update_events(); + pl__update_keyboard_inputs(); + pl__update_mouse_inputs(); + + // update state id's from previous frame + // gtIO.bWantCaptureKeyboard = gptCtx->uActiveId != 0; + // gtIO.bWantCaptureMouse = gtIO._abMouseOwned[0] || gptCtx->uActiveId != 0 || gptCtx->ptMovingWindow != NULL; + + // track click ownership + // for(uint32_t i = 0; i < 5; i++) + // { + // if(gtIO._abMouseClicked[i]) + // { + // gtIO._abMouseOwned[i] = (gptCtx->ptHoveredWindow != NULL); + // } + // } +} + +//----------------------------------------------------------------------------- +// [SECTION] public api implementation +//----------------------------------------------------------------------------- + +const plApiRegistryI* +pl_load_core_apis(void) +{ + + const plApiRegistryI* ptApiRegistry = pl__load_api_registry(); + pl__create_mutex(&gptDataMutex); + + pl_sb_resize(gtDataRegistryData.sbtFreeDataIDs, 1024); + for(uint32_t i = 0; i < 1024; i++) + { + gtDataRegistryData.sbtFreeDataIDs[i].uIndex = i; + } + + static const plIOI tIOApi = { + .new_frame = pl_new_frame, + .get_io = pl_get_io, + .is_key_down = pl_is_key_down, + .is_key_pressed = pl_is_key_pressed, + .is_key_released = pl_is_key_released, + .get_key_pressed_amount = pl_get_key_pressed_amount, + .is_mouse_down = pl_is_mouse_down, + .is_mouse_clicked = pl_is_mouse_clicked, + .is_mouse_released = pl_is_mouse_released, + .is_mouse_double_clicked = pl_is_mouse_double_clicked, + .is_mouse_dragging = pl_is_mouse_dragging, + .is_mouse_hovering_rect = pl_is_mouse_hovering_rect, + .reset_mouse_drag_delta = pl_reset_mouse_drag_delta, + .get_mouse_drag_delta = pl_get_mouse_drag_delta, + .get_mouse_pos = pl_get_mouse_pos, + .get_mouse_wheel = pl_get_mouse_wheel, + .is_mouse_pos_valid = pl_is_mouse_pos_valid, + .set_mouse_cursor = pl_set_mouse_cursor, + .get_key_data = pl_get_key_data, + .add_key_event = pl_add_key_event, + .add_text_event = pl_add_text_event, + .add_text_event_utf16 = pl_add_text_event_utf16, + .add_text_events_utf8 = pl_add_text_events_utf8, + .add_mouse_pos_event = pl_add_mouse_pos_event, + .add_mouse_button_event = pl_add_mouse_button_event, + .add_mouse_wheel_event = pl_add_mouse_wheel_event, + .clear_input_characters = pl_clear_input_characters, + }; + + static const plDataRegistryI tDataRegistryApi = { + .set_data = pl__set_data, + .get_data = pl__get_data, + .garbage_collect = pl__garbage_collect, + .create_object = pl__create_object, + .get_object_by_name = pl__get_object_by_name, + .read = pl__read, + .end_read = pl__end_read, + .get_string = pl__get_string, + .get_buffer = pl__get_buffer, + .write = pl__write, + .set_string = pl__set_string, + .set_buffer = pl__set_buffer, + .commit = pl__commit + }; + + static const plExtensionRegistryI tExtensionRegistryApi = { + .load = pl__load_extension, + .unload = pl__unload_extension, + .unload_all = pl__unload_all_extensions, + .reload = pl__handle_extension_reloads + }; + + // apis more likely to not be stored, should be first (api registry is not sorted) + ptApiRegistry->add(PL_API_IO, &tIOApi); + ptApiRegistry->add(PL_API_DATA_REGISTRY, &tDataRegistryApi); + ptApiRegistry->add(PL_API_EXTENSION_REGISTRY, &tExtensionRegistryApi); + + return ptApiRegistry; +} + +void +pl_unload_core_apis(void) +{ + const uint32_t uApiCount = pl_sb_size(gsbApiEntries); + for(uint32_t i = 0; i < uApiCount; i++) + { + pl_sb_free(gsbApiEntries[i].sbSubscribers); + pl_sb_free(gsbApiEntries[i].sbUserData); + } + + pl_sb_free(gsbtExtensions); + pl_sb_free(gsbptLibs); + pl_sb_free(gsbtHotLibs); + pl_sb_free(gsbApiEntries); + pl_hm_free(>HashMap); +} + + #ifdef PL_USE_STB_SPRINTF #define STB_SPRINTF_IMPLEMENTATION #include "stb_sprintf.h" @@ -624,9 +1268,3 @@ pl_realloc(void* pBuffer, size_t szSize, const char* pcFile, int iLine) { return realloc(pBuffer, szSize); } - -#ifdef PL_USE_UI -#include "pl_ui.c" -#include "pl_ui_widgets.c" -#include "pl_ui_draw.c" -#endif \ No newline at end of file diff --git a/src/pilotlight_lib.c b/src/pilotlight_lib.c index 6f62956d..ebc3d152 100644 --- a/src/pilotlight_lib.c +++ b/src/pilotlight_lib.c @@ -115,11 +115,4 @@ pl_realloc(void* pBuffer, size_t szSize, const char* pcFile, int iLine) gptMemoryContext->plThreadsI->unlock_mutex(gptMutex); return pNewBuffer; -} - -#ifdef PL_USE_UI -#include "pl_ui.c" -#include "pl_ui_widgets.c" -#include "pl_ui_demo.c" -#include "pl_ui_draw.c" -#endif \ No newline at end of file +} \ No newline at end of file diff --git a/src/pl_config.h b/src/pl_config.h index 717e13b1..32729cd4 100644 --- a/src/pl_config.h +++ b/src/pl_config.h @@ -41,7 +41,4 @@ #define PL_LOG_ERROR_FG_COLOR PL_LOG_FG_COLOR_CODE_RED #define PL_LOG_FATAL_FG_COLOR PL_LOG_FG_COLOR_CODE_WHITE -// UI -#define PL_USE_UI - #endif // PL_CONFIG_H \ No newline at end of file diff --git a/src/pl_main_macos.m b/src/pl_main_macos.m index 4658f224..58a6d017 100644 --- a/src/pl_main_macos.m +++ b/src/pl_main_macos.m @@ -19,7 +19,6 @@ //----------------------------------------------------------------------------- #include "pilotlight.h" // data registry, api registry, extension registry -#include "pl_ui.h" // io context #include "pl_os.h" #include "pl_ds.h" // hashmap @@ -251,6 +250,7 @@ + (id)_windowResizeEastWestCursor; static const plDataRegistryI* gptDataRegistry = NULL; static const plApiRegistryI* gptApiRegistry = NULL; static const plExtensionRegistryI* gptExtensionRegistry = NULL; +static const plIOI* gptIOI = NULL; // OS apis static const plLibraryI* gptLibraryApi = NULL; @@ -265,7 +265,6 @@ + (id)_windowResizeEastWestCursor; // ui plIO* gptIOCtx = NULL; -plUiContext* gptUiCtx = NULL; // memory tracking static plMemoryContext gtMemoryContext = {0}; @@ -330,9 +329,6 @@ int main(int argc, char *argv[]) #endif - gptUiCtx = pl_create_context(); - gptIOCtx = pl_get_io(); - // load apis gtMemoryContext.ptHashMap = >MemoryHashMap; gptApiRegistry = pl_load_core_apis(); @@ -415,9 +411,10 @@ int main(int argc, char *argv[]) gptDataRegistry = gptApiRegistry->first(PL_API_DATA_REGISTRY); gptExtensionRegistry = gptApiRegistry->first(PL_API_EXTENSION_REGISTRY); gptLibraryApi = gptApiRegistry->first(PL_API_LIBRARY); + gptIOI = gptApiRegistry->first(PL_API_IO); // setup & retrieve io context - gptDataRegistry->set_data("context", gptUiCtx); + gptIOCtx = gptIOI->get_io(); gtMemoryContext.plThreadsI = &tThreadApi; gptDataRegistry->set_data(PL_CONTEXT_MEMORY, >MemoryContext); @@ -765,7 +762,7 @@ - (void)renderToMetalLayer:(nonnull CAMetalLayer *)layer if(gptIOCtx->bRunning == false) { - [NSApp stop]; + [NSApp terminate:nil]; } } } @@ -842,7 +839,7 @@ - (void)insertText:(id)aString replacementRange:(NSRange)replacementRange else characters = (NSString*)aString; - pl_add_text_events_utf8(characters.UTF8String); + gptIOI->add_text_events_utf8(characters.UTF8String); } - (BOOL)acceptsFirstResponder @@ -906,7 +903,7 @@ - (void)unmarkText { int button = (int)[event buttonNumber]; if (button >= 0 && button < PL_MOUSE_BUTTON_COUNT) - pl_add_mouse_button_event(button, true); + gptIOI->add_mouse_button_event(button, true); return true; } @@ -914,7 +911,7 @@ - (void)unmarkText { int button = (int)[event buttonNumber]; if (button >= 0 && button < PL_MOUSE_BUTTON_COUNT) - pl_add_mouse_button_event(button, false); + gptIOI->add_mouse_button_event(button, false); return true; } @@ -926,7 +923,7 @@ - (void)unmarkText mousePoint = NSMakePoint(mousePoint.x, mousePoint.y); else mousePoint = NSMakePoint(mousePoint.x, view.bounds.size.height - mousePoint.y); - pl_add_mouse_pos_event((float)mousePoint.x, (float)mousePoint.y); + gptIOI->add_mouse_pos_event((float)mousePoint.x, (float)mousePoint.y); return true; } @@ -968,7 +965,7 @@ - (void)unmarkText wheel_dy = [event deltaY]; } if (wheel_dx != 0.0 || wheel_dy != 0.0) - pl_add_mouse_wheel_event((float)wheel_dx * 0.1f, (float)wheel_dy * 0.1f); + gptIOI->add_mouse_wheel_event((float)wheel_dx * 0.1f, (float)wheel_dy * 0.1f); return true; } @@ -979,7 +976,7 @@ - (void)unmarkText return true; int key_code = (int)[event keyCode]; - pl_add_key_event(pl__osx_key_to_pl_key(key_code), event.type == NSEventTypeKeyDown); + gptIOI->add_key_event(pl__osx_key_to_pl_key(key_code), event.type == NSEventTypeKeyDown); return true; } @@ -988,10 +985,10 @@ - (void)unmarkText unsigned short key_code = [event keyCode]; NSEventModifierFlags modifier_flags = [event modifierFlags]; - pl_add_key_event(PL_KEY_MOD_SHIFT, (modifier_flags & NSEventModifierFlagShift) != 0); - pl_add_key_event(PL_KEY_MOD_CTRL, (modifier_flags & NSEventModifierFlagControl) != 0); - pl_add_key_event(PL_KEY_MOD_ALT, (modifier_flags & NSEventModifierFlagOption) != 0); - pl_add_key_event(PL_KEY_MOD_SUPER, (modifier_flags & NSEventModifierFlagCommand) != 0); + gptIOI->add_key_event(PL_KEY_MOD_SHIFT, (modifier_flags & NSEventModifierFlagShift) != 0); + gptIOI->add_key_event(PL_KEY_MOD_CTRL, (modifier_flags & NSEventModifierFlagControl) != 0); + gptIOI->add_key_event(PL_KEY_MOD_ALT, (modifier_flags & NSEventModifierFlagOption) != 0); + gptIOI->add_key_event(PL_KEY_MOD_SUPER, (modifier_flags & NSEventModifierFlagCommand) != 0); plKey key = pl__osx_key_to_pl_key(key_code); if (key != PL_KEY_NONE) @@ -1013,7 +1010,7 @@ - (void)unmarkText } NSEventModifierFlags modifier_flags = [event modifierFlags]; - pl_add_key_event(key, (modifier_flags & mask) != 0); + gptIOI->add_key_event(key, (modifier_flags & mask) != 0); // io.SetKeyEventNativeData(key, key_code, -1); // To support legacy indexing (<1.87 user code) } diff --git a/src/pl_main_win32.c b/src/pl_main_win32.c index f8dbb784..113f124c 100644 --- a/src/pl_main_win32.c +++ b/src/pl_main_win32.c @@ -42,7 +42,6 @@ Index of this file: //----------------------------------------------------------------------------- #include "pilotlight.h" // data registry, api registry, extension registry -#include "pl_ui.h" // io context #include "pl_os.h" // os apis #include "pl_ds.h" // hashmap @@ -207,12 +206,12 @@ INT64 ilTicksPerSecond = 0; HWND tMouseHandle = NULL; bool bMouseTracked = true; plIO* gptIOCtx = NULL; -plUiContext* gptUiCtx = NULL; // apis const plDataRegistryI* gptDataRegistry = NULL; const plApiRegistryI* gptApiRegistry = NULL; const plExtensionRegistryI* gptExtensionRegistry = NULL; +const plIOI* gptIOI = NULL; // memory tracking plHashMap gtMemoryHashMap = {0}; @@ -235,8 +234,6 @@ void (*pl_app_update) (void* userData); int main(int argc, char *argv[]) { const char* pcAppName = "app"; - gptUiCtx = pl_create_context(); - gptIOCtx = pl_get_io(); for(int i = 1; i < argc; i++) { @@ -355,6 +352,7 @@ int main(int argc, char *argv[]) gptApiRegistry = pl_load_core_apis(); gptDataRegistry = gptApiRegistry->first(PL_API_DATA_REGISTRY); gptExtensionRegistry = gptApiRegistry->first(PL_API_EXTENSION_REGISTRY); + gptIOI = gptApiRegistry->first(PL_API_IO); // add os specific apis gptApiRegistry->add(PL_API_WINDOW, &tWindowApi); @@ -365,11 +363,11 @@ int main(int argc, char *argv[]) gptApiRegistry->add(PL_API_ATOMICS, &tAtomicsApi); // set clipboard functions (may need to move this to OS api) + gptIOCtx = gptIOI->get_io(); gptIOCtx->set_clipboard_text_fn = pl__set_clipboard_text; gptIOCtx->get_clipboard_text_fn = pl__get_clipboard_text; // add contexts to data registry - gptDataRegistry->set_data("context", gptUiCtx); gtMemoryContext.plThreadsI = &tThreadApi; gptDataRegistry->set_data(PL_CONTEXT_MEMORY, >MemoryContext); @@ -587,13 +585,13 @@ pl__windows_procedure(HWND tHwnd, UINT tMsg, WPARAM tWParam, LPARAM tLParam) { // You can also use ToAscii()+GetKeyboardState() to retrieve characters. if (tWParam > 0 && tWParam < 0x10000) - pl_add_text_event_utf16((uint16_t)tWParam); + gptIOI->add_text_event_utf16((uint16_t)tWParam); } else { wchar_t wch = 0; MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, (char*)&tWParam, 1, &wch, 1); - pl_add_text_event(wch); + gptIOI->add_text_event(wch); } break; @@ -628,7 +626,7 @@ pl__windows_procedure(HWND tHwnd, UINT tMsg, WPARAM tWParam, LPARAM tLParam) bMouseTracked = true; } POINT tMousePos = { (LONG)GET_X_LPARAM(tLParam), (LONG)GET_Y_LPARAM(tLParam) }; - pl_add_mouse_pos_event((float)tMousePos.x, (float)tMousePos.y); + gptIOI->add_mouse_pos_event((float)tMousePos.x, (float)tMousePos.y); break; } case WM_MOUSELEAVE: @@ -636,7 +634,7 @@ pl__windows_procedure(HWND tHwnd, UINT tMsg, WPARAM tWParam, LPARAM tLParam) if(tHwnd == tMouseHandle) { tMouseHandle = NULL; - pl_add_mouse_pos_event(-FLT_MAX, -FLT_MAX); + gptIOI->add_mouse_pos_event(-FLT_MAX, -FLT_MAX); } bMouseTracked = false; break; @@ -655,7 +653,7 @@ pl__windows_procedure(HWND tHwnd, UINT tMsg, WPARAM tWParam, LPARAM tLParam) if(gptIOCtx->_iMouseButtonsDown == 0 && GetCapture() == NULL) SetCapture(tHwnd); gptIOCtx->_iMouseButtonsDown |= 1 << iButton; - pl_add_mouse_button_event(iButton, true); + gptIOI->add_mouse_button_event(iButton, true); break; } case WM_LBUTTONUP: @@ -671,19 +669,19 @@ pl__windows_procedure(HWND tHwnd, UINT tMsg, WPARAM tWParam, LPARAM tLParam) gptIOCtx->_iMouseButtonsDown &= ~(1 << iButton); if(gptIOCtx->_iMouseButtonsDown == 0 && GetCapture() == tHwnd) ReleaseCapture(); - pl_add_mouse_button_event(iButton, false); + gptIOI->add_mouse_button_event(iButton, false); break; } case WM_MOUSEWHEEL: { - pl_add_mouse_wheel_event(0.0f, (float)GET_WHEEL_DELTA_WPARAM(tWParam) / (float)WHEEL_DELTA); + gptIOI->add_mouse_wheel_event(0.0f, (float)GET_WHEEL_DELTA_WPARAM(tWParam) / (float)WHEEL_DELTA); break; } case WM_MOUSEHWHEEL: { - pl_add_mouse_wheel_event((float)GET_WHEEL_DELTA_WPARAM(tWParam) / (float)WHEEL_DELTA, 0.0f); + gptIOI->add_mouse_wheel_event((float)GET_WHEEL_DELTA_WPARAM(tWParam) / (float)WHEEL_DELTA, 0.0f); break; } @@ -697,10 +695,10 @@ pl__windows_procedure(HWND tHwnd, UINT tMsg, WPARAM tWParam, LPARAM tLParam) { // Submit modifiers - pl_add_key_event(PL_KEY_MOD_CTRL, pl__is_vk_down(VK_CONTROL)); - pl_add_key_event(PL_KEY_MOD_SHIFT, pl__is_vk_down(VK_SHIFT)); - pl_add_key_event(PL_KEY_MOD_ALT, pl__is_vk_down(VK_MENU)); - pl_add_key_event(PL_KEY_MOD_SUPER, pl__is_vk_down(VK_APPS)); + gptIOI->add_key_event(PL_KEY_MOD_CTRL, pl__is_vk_down(VK_CONTROL)); + gptIOI->add_key_event(PL_KEY_MOD_SHIFT, pl__is_vk_down(VK_SHIFT)); + gptIOI->add_key_event(PL_KEY_MOD_ALT, pl__is_vk_down(VK_MENU)); + gptIOI->add_key_event(PL_KEY_MOD_SUPER, pl__is_vk_down(VK_APPS)); // obtain virtual key code int iVk = (int)tWParam; @@ -711,23 +709,23 @@ pl__windows_procedure(HWND tHwnd, UINT tMsg, WPARAM tWParam, LPARAM tLParam) const plKey tKey = pl__virtual_key_to_pl_key(iVk); if (tKey != PL_KEY_NONE) - pl_add_key_event(tKey, bKeyDown); + gptIOI->add_key_event(tKey, bKeyDown); // Submit individual left/right modifier events if (iVk == VK_SHIFT) { - if (pl__is_vk_down(VK_LSHIFT) == bKeyDown) pl_add_key_event(PL_KEY_LEFT_SHIFT, bKeyDown); - if (pl__is_vk_down(VK_RSHIFT) == bKeyDown) pl_add_key_event(PL_KEY_RIGHT_SHIFT, bKeyDown); + if (pl__is_vk_down(VK_LSHIFT) == bKeyDown) gptIOI->add_key_event(PL_KEY_LEFT_SHIFT, bKeyDown); + if (pl__is_vk_down(VK_RSHIFT) == bKeyDown) gptIOI->add_key_event(PL_KEY_RIGHT_SHIFT, bKeyDown); } else if (iVk == VK_CONTROL) { - if (pl__is_vk_down(VK_LCONTROL) == bKeyDown) pl_add_key_event(PL_KEY_LEFT_CTRL, bKeyDown); - if (pl__is_vk_down(VK_RCONTROL) == bKeyDown) pl_add_key_event(PL_KEY_RIGHT_CTRL, bKeyDown); + if (pl__is_vk_down(VK_LCONTROL) == bKeyDown) gptIOI->add_key_event(PL_KEY_LEFT_CTRL, bKeyDown); + if (pl__is_vk_down(VK_RCONTROL) == bKeyDown) gptIOI->add_key_event(PL_KEY_RIGHT_CTRL, bKeyDown); } else if (iVk == VK_MENU) { - if (pl__is_vk_down(VK_LMENU) == bKeyDown) pl_add_key_event(PL_KEY_LEFT_ALT, bKeyDown); - if (pl__is_vk_down(VK_RMENU) == bKeyDown) pl_add_key_event(PL_KEY_RIGHT_ALT, bKeyDown); + if (pl__is_vk_down(VK_LMENU) == bKeyDown) gptIOI->add_key_event(PL_KEY_LEFT_ALT, bKeyDown); + if (pl__is_vk_down(VK_RMENU) == bKeyDown) gptIOI->add_key_event(PL_KEY_RIGHT_ALT, bKeyDown); } } break; diff --git a/ui/pl_ui_demo.c b/ui/pl_ui_demo.c new file mode 100644 index 00000000..ffd2a0d0 --- /dev/null +++ b/ui/pl_ui_demo.c @@ -0,0 +1,527 @@ +#include "pl_ui_internal.h" + +void +pl_show_debug_window(bool* pbOpen) +{ + // tools + static bool bShowWindowOuterRect = false; + static bool bShowWindowOuterClippedRect = false; + static bool bShowWindowInnerRect = false; + static bool bShowWindowInnerClipRect = false; + + if(pl_begin_window("Pilot Light UI Metrics/Debugger", pbOpen, 0)) + { + + const float pfRatios[] = {1.0f}; + pl_layout_row(PL_UI_LAYOUT_ROW_TYPE_DYNAMIC, 0.0f, 1, pfRatios); + + pl_separator(); + + pl_checkbox("Show Window Inner Rect", &bShowWindowInnerRect); + pl_checkbox("Show Window Inner Clip Rect", &bShowWindowInnerClipRect); + pl_checkbox("Show Window Outer Rect", &bShowWindowOuterRect); + pl_checkbox("Show Window Outer Rect Clipped", &bShowWindowOuterClippedRect); + + for(uint32_t uWindowIndex = 0; uWindowIndex < pl_sb_size(gptCtx->sbptWindows); uWindowIndex++) + { + const plUiWindow* ptWindow = gptCtx->sbptWindows[uWindowIndex]; + + if(ptWindow->bActive) + { + if(bShowWindowInnerRect) + gptDraw->add_rect(gptCtx->ptDebugLayer, ptWindow->tInnerRect.tMin, ptWindow->tInnerRect.tMax, (plVec4){1.0f, 1.0f, 0.0f, 1.0f}, 1.0f); + + if(bShowWindowInnerClipRect) + gptDraw->add_rect(gptCtx->ptDebugLayer, ptWindow->tInnerClipRect.tMin, ptWindow->tInnerClipRect.tMax, (plVec4){1.0f, 1.0f, 0.0f, 1.0f}, 1.0f); + + if(bShowWindowOuterRect) + gptDraw->add_rect(gptCtx->ptDebugLayer, ptWindow->tOuterRect.tMin, ptWindow->tOuterRect.tMax, (plVec4){1.0f, 1.0f, 0.0f, 1.0f}, 1.0f); + + if(bShowWindowOuterClippedRect) + gptDraw->add_rect(gptCtx->ptDebugLayer, ptWindow->tOuterRectClipped.tMin, ptWindow->tOuterRectClipped.tMax, (plVec4){1.0f, 1.0f, 0.0f, 1.0f}, 1.0f); + } + } + + pl_separator(); + + if(pl_tree_node("Windows")) + { + for(uint32_t uWindowIndex = 0; uWindowIndex < pl_sb_size(gptCtx->sbptWindows); uWindowIndex++) + { + const plUiWindow* ptWindow = gptCtx->sbptWindows[uWindowIndex]; + + if(pl_tree_node(ptWindow->pcName)) + { + pl_text(" - Pos: (%0.1f, %0.1f)", ptWindow->tPos.x, ptWindow->tPos.y); + pl_text(" - Size: (%0.1f, %0.1f)", ptWindow->tSize.x, ptWindow->tSize.y); + pl_text(" - Content Size: (%0.1f, %0.1f)", ptWindow->tContentSize.x, ptWindow->tContentSize.y); + pl_text(" - Min Size: (%0.1f, %0.1f)", ptWindow->tMinSize.x, ptWindow->tMinSize.y); + pl_text(" - Scroll: (%0.1f/%0.1f, %0.1f/%0.1f)", ptWindow->tScroll.x, ptWindow->tScrollMax.x, ptWindow->tScroll.y, ptWindow->tScrollMax.y); + pl_text(" - Active: %s", ptWindow->uId == gptCtx->uActiveWindowId ? "1" : "0"); + pl_text(" - Hovered: %s", ptWindow == gptCtx->ptHoveredWindow ? "1" : "0"); + pl_text(" - Dragging: %s", ptWindow == gptCtx->ptMovingWindow ? "1" : "0"); + pl_text(" - Scrolling: %s", ptWindow == gptCtx->ptWheelingWindow ? "1" : "0"); + pl_text(" - Resizing: %s", ptWindow == gptCtx->ptSizingWindow ? "1" : "0"); + pl_text(" - Collapsed: %s", ptWindow->bCollapsed ? "1" : "0"); + pl_text(" - Auto Sized: %s", ptWindow->tFlags & PL_UI_WINDOW_FLAGS_AUTO_SIZE ? "1" : "0"); + + pl_tree_pop(); + } + } + pl_tree_pop(); + } + if(pl_tree_node("Internal State")) + { + pl_text("Windows"); + pl_indent(0.0f); + pl_text("Active Window: %s", gptCtx->ptActiveWindow ? gptCtx->ptActiveWindow->pcName : "NULL"); + pl_text("Hovered Window: %s", gptCtx->ptHoveredWindow ? gptCtx->ptHoveredWindow->pcName : "NULL"); + pl_text("Moving Window: %s", gptCtx->ptMovingWindow ? gptCtx->ptMovingWindow->pcName : "NULL"); + pl_text("Sizing Window: %s", gptCtx->ptSizingWindow ? gptCtx->ptSizingWindow->pcName : "NULL"); + pl_text("Scrolling Window: %s", gptCtx->ptScrollingWindow ? gptCtx->ptScrollingWindow->pcName : "NULL"); + pl_text("Wheeling Window: %s", gptCtx->ptWheelingWindow ? gptCtx->ptWheelingWindow->pcName : "NULL"); + pl_unindent(0.0f); + pl_text("Items"); + pl_indent(0.0f); + pl_text("Active Window ID: %u", gptCtx->uActiveWindowId); + pl_text("Active ID: %u", gptCtx->uActiveId); + pl_text("Hovered ID: %u", gptCtx->uHoveredId); + pl_unindent(0.0f); + pl_tree_pop(); + } + pl_end_window(); + } +} + +void +pl_show_style_editor_window(bool* pbOpen) +{ + + if(pl_begin_window("Pilot Light UI Style", pbOpen, 0)) + { + + const float pfRatios[] = {1.0f}; + pl_layout_row(PL_UI_LAYOUT_ROW_TYPE_DYNAMIC, 0.0f, 1, pfRatios); + + plUiStyle* ptStyle = &gptCtx->tStyle; + + if(pl_begin_tab_bar("Tabs")) + { + if(pl_begin_tab("Colors")) + { + pl_end_tab(); + } + + if(pl_begin_tab("Sizes")) + { + pl_vertical_spacing(); + pl_text("Title"); + pl_slider_float("Title Padding", &ptStyle->fTitlePadding, 0.0f, 32.0f); + + pl_vertical_spacing(); + pl_text("Window"); + pl_slider_float("Horizontal Padding## window", &ptStyle->fWindowHorizontalPadding, 0.0f, 32.0f); + pl_slider_float("Vertical Padding## window", &ptStyle->fWindowVerticalPadding, 0.0f, 32.0f); + + pl_vertical_spacing(); + pl_text("Scrollbar"); + pl_slider_float("Size##scrollbar", &ptStyle->fScrollbarSize, 0.0f, 32.0f); + + pl_vertical_spacing(); + pl_text("Misc"); + pl_slider_float("Indent", &ptStyle->fIndentSize, 0.0f, 32.0f); + pl_slider_float("Slider Size", &ptStyle->fSliderSize, 3.0f, 32.0f); + pl_slider_float("Font Size", &ptStyle->fFontSize, 13.0f, 48.0f); + pl_end_tab(); + } + pl_end_tab_bar(); + } + pl_end_window(); + } +} + +void +pl_show_demo_window(bool* pbOpen) +{ + if(pl_begin_window("UI Demo", pbOpen, 0)) + { + + static const float pfRatios0[] = {1.0f}; + pl_layout_row(PL_UI_LAYOUT_ROW_TYPE_DYNAMIC, 0.0f, 1, pfRatios0); + + pl_text("Pilot Light UI v%s", PL_UI_EXT_VERSION); + + if(pl_collapsing_header("Help")) + { + pl_text("Under construction"); + pl_end_collapsing_header(); + } + + if(pl_collapsing_header("Window Options")) + { + pl_text("Under construction"); + pl_end_collapsing_header(); + } + + if(pl_collapsing_header("Widgets")) + { + if(pl_tree_node("Basic")) + { + + pl_layout_static(0.0f, 100, 2); + pl_button("Button"); + pl_checkbox("Checkbox", NULL); + + pl_layout_dynamic(0.0f, 2); + pl_button("Button"); + pl_checkbox("Checkbox", NULL); + + pl_layout_dynamic(0.0f, 1); + static char buff[64] = {'c', 'a', 'a'}; + pl_input_text("label 0", buff, 64); + static char buff2[64] = {'c', 'c', 'c'}; + pl_input_text_hint("label 1", "hint", buff2, 64); + + static float fValue = 3.14f; + static int iValue117 = 117; + + pl_input_float("label 2", &fValue, "%0.3f"); + pl_input_int("label 3", &iValue117); + + static int iValue = 0; + pl_layout_row_begin(PL_UI_LAYOUT_ROW_TYPE_DYNAMIC, 0.0f, 3); + + pl_layout_row_push(0.33f); + pl_radio_button("Option 1", &iValue, 0); + + pl_layout_row_push(0.33f); + pl_radio_button("Option 2", &iValue, 1); + + pl_layout_row_push(0.34f); + pl_radio_button("Option 3", &iValue, 2); + + pl_layout_row_end(); + + const float pfRatios[] = {1.0f}; + pl_layout_row(PL_UI_LAYOUT_ROW_TYPE_DYNAMIC, 0.0f, 1, pfRatios); + pl_separator(); + pl_labeled_text("Label", "Value"); + static int iValue1 = 0; + static float fValue1 = 23.0f; + static float fValue2 = 100.0f; + static int iValue2 = 3; + pl_slider_float("float slider 1", &fValue1, 0.0f, 100.0f); + pl_slider_float("float slider 2", &fValue2, -50.0f, 100.0f); + pl_slider_int("int slider 1", &iValue1, 0, 10); + pl_slider_int("int slider 2", &iValue2, -5, 10); + pl_drag_float("float drag", &fValue2, 1.0f, -100.0f, 100.0f); + + const float pfRatios22[] = {100.0f, 120.0f}; + pl_layout_row(PL_UI_LAYOUT_ROW_TYPE_STATIC, 0.0f, 2, pfRatios22); + pl_button("Hover me!"); + if(pl_was_last_item_hovered()) + { + pl_begin_tooltip(); + pl_layout_row(PL_UI_LAYOUT_ROW_TYPE_STATIC, 0.0f, 1, pfRatios22); + pl_text("I'm a tooltip!"); + pl_end_tooltip(); + } + pl_button("Just a button"); + + pl_tree_pop(); + } + + if(pl_tree_node("Selectables")) + { + static bool bSelectable0 = false; + static bool bSelectable1 = false; + static bool bSelectable2 = false; + pl_selectable("Selectable 1", &bSelectable0); + pl_selectable("Selectable 2", &bSelectable1); + pl_selectable("Selectable 3", &bSelectable2); + pl_tree_pop(); + } + + if(pl_tree_node("Plotting")) + { + pl_progress_bar(0.75f, (plVec2){-1.0f, 0.0f}, NULL); + pl_tree_pop(); + } + + if(pl_tree_node("Trees")) + { + + if(pl_tree_node("Root Node")) + { + if(pl_tree_node("Child 1")) + { + pl_button("Press me"); + pl_tree_pop(); + } + if(pl_tree_node("Child 2")) + { + pl_button("Press me"); + pl_tree_pop(); + } + pl_tree_pop(); + } + pl_tree_pop(); + } + + if(pl_tree_node("Tabs")) + { + if(pl_begin_tab_bar("Tabs1")) + { + if(pl_begin_tab("Tab 0")) + { + static bool bSelectable0 = false; + static bool bSelectable1 = false; + static bool bSelectable2 = false; + pl_selectable("Selectable 1", &bSelectable0); + pl_selectable("Selectable 2", &bSelectable1); + pl_selectable("Selectable 3", &bSelectable2); + pl_end_tab(); + } + + if(pl_begin_tab("Tab 1")) + { + static int iValue = 0; + pl_radio_button("Option 1", &iValue, 0); + pl_radio_button("Option 2", &iValue, 1); + pl_radio_button("Option 3", &iValue, 2); + pl_end_tab(); + } + + if(pl_begin_tab("Tab 2")) + { + if(pl_begin_child("CHILD2")) + { + const float pfRatios3[] = {600.0f}; + pl_layout_row(PL_UI_LAYOUT_ROW_TYPE_STATIC, 0.0f, 1, pfRatios3); + + for(uint32_t i = 0; i < 25; i++) + pl_text("Long text is happening11111111111111111111111111111111111111111111111111111111123456789"); + pl_end_child(); + } + + pl_end_tab(); + } + pl_end_tab_bar(); + } + pl_tree_pop(); + } + pl_end_collapsing_header(); + } + + if(pl_collapsing_header("Layout & Scrolling")) + { + const float pfRatios2[] = {0.5f, 0.50f}; + const float pfRatios3[] = {600.0f}; + + pl_layout_static(0.0f, 100, 1); + static bool bUseClipper = true; + pl_checkbox("Use Clipper", &bUseClipper); + + pl_layout_row(PL_UI_LAYOUT_ROW_TYPE_DYNAMIC, 300.0f, 2, pfRatios2); + if(pl_begin_child("CHILD")) + { + + pl_layout_row(PL_UI_LAYOUT_ROW_TYPE_DYNAMIC, 0.0f, 2, pfRatios2); + + + if(bUseClipper) + { + plUiClipper tClipper = {1000000}; + while(pl_step_clipper(&tClipper)) + { + for(uint32_t i = tClipper.uDisplayStart; i < tClipper.uDisplayEnd; i++) + { + pl_text("%u Label", i); + pl_text("%u Value", i); + } + } + } + else + { + for(uint32_t i = 0; i < 1000000; i++) + { + pl_text("%u Label", i); + pl_text("%u Value", i); + } + } + + + pl_end_child(); + } + + + if(pl_begin_child("CHILD2")) + { + pl_layout_row(PL_UI_LAYOUT_ROW_TYPE_STATIC, 0.0f, 1, pfRatios3); + + for(uint32_t i = 0; i < 25; i++) + pl_text("Long text is happening11111111111111111111111111111111111111111111111111111111123456789"); + + pl_end_child(); + } + + + pl_end_collapsing_header(); + } + + + if(pl_collapsing_header("Testing 0")) + { + // first row + pl_layout_row_begin(PL_UI_LAYOUT_ROW_TYPE_STATIC, 60.0f, 2); + + pl_layout_row_push(200.0f); + pl_button("Hover me 60!"); + + pl_layout_row_push(200.0f); + pl_button("Hover me 40"); + + pl_layout_row_end(); + + // space + pl_layout_space_begin(PL_UI_LAYOUT_ROW_TYPE_STATIC, 500.0f, UINT32_MAX); + + pl_layout_space_push(0.0f, 0.0f, 100.0f, 100.0f); + pl_button("Hover me A!"); + + pl_layout_space_push(105.0f, 105.0f, 100.0f, 100.0f); + pl_button("Hover me B!"); + + pl_layout_space_end(); + + // space + pl_layout_space_begin(PL_UI_LAYOUT_ROW_TYPE_DYNAMIC, 500.0f, 2); + + pl_layout_space_push(0.0f, 0.0f, 0.5f, 0.1f); + pl_button("Hover me AA!"); + + pl_layout_space_push(0.25f, 0.50f, 0.5f, 0.1f); + pl_button("Hover me BB!"); + + pl_layout_space_end(); + + pl_end_collapsing_header(); + } + + if(pl_collapsing_header("Testing 1")) + { + // first row + pl_layout_row_begin(PL_UI_LAYOUT_ROW_TYPE_STATIC, 60.0f, 2); + + pl_layout_row_push(200.0f); + pl_button("Hover me 60!"); + + pl_layout_row_push(200.0f); + pl_button("Hover me 40"); + + pl_layout_row_end(); + + // second row + pl_layout_row_begin(PL_UI_LAYOUT_ROW_TYPE_STATIC, 100.0f, 3); + + pl_layout_row_push(100.0f); + pl_button("Hover me 100!"); + + pl_layout_row_push(200.0f); + pl_button("Hover me 200"); + + pl_layout_row_push(300.0f); + pl_button("Hover me 300"); + + pl_layout_row_end(); + + + // third row + pl_layout_row_begin(PL_UI_LAYOUT_ROW_TYPE_DYNAMIC, 0.0f, 3); + + pl_layout_row_push(0.33f); + pl_button("Hover me 100!"); + + pl_layout_row_push(0.33f); + pl_button("Hover me 200"); + + pl_layout_row_push(0.34f); + pl_button("Hover me 300"); + + pl_layout_row_end(); + + // fourth & fifth row + const float pfRatios33[] = {0.25f, 0.25f, 0.50f}; + pl_layout_row(PL_UI_LAYOUT_ROW_TYPE_DYNAMIC, 0.0f, 3, pfRatios33); + + // row 4 + pl_button("Hover me 100a!"); + pl_button("Hover me 200a"); + pl_button("Hover me 300a"); + + // row 5 + pl_button("Hover me 100b!"); + pl_button("Hover me 200b"); + pl_button("Hover me 300b"); + + // sixth + const float pfRatios2[] = {100.0f, 100.0f}; + pl_layout_row(PL_UI_LAYOUT_ROW_TYPE_STATIC, 0.0f, 2, pfRatios2); + + // row 6 + pl_button("Hover me 100c!"); + pl_button("Hover me 200c"); + + pl_end_collapsing_header(); + } + + if(pl_collapsing_header("Testing 2")) + { + pl_layout_template_begin(60.0f); + pl_layout_template_push_static(100.0f); + pl_layout_template_push_variable(500.0f); + pl_layout_template_push_dynamic(); + pl_layout_template_end(); + + pl_button("static"); + pl_button("variable"); + pl_button("dynamic"); + + pl_button("static"); + pl_button("variable"); + pl_button("dynamic"); + + pl_layout_template_begin(30.0f); + pl_layout_template_push_static(100.0f); + pl_layout_template_push_dynamic(); + pl_layout_template_push_variable(500.0f); + pl_layout_template_end(); + + pl_button("static"); + pl_button("dynamic"); + pl_button("variable"); + + pl_button("static##11"); + pl_button("dynamic##11"); + pl_button("variable##11"); + + pl_layout_template_begin(0.0f); + pl_layout_template_push_variable(500.0f); + pl_layout_template_push_dynamic(); + pl_layout_template_push_static(100.0f); + pl_layout_template_end(); + + + pl_button("variable##11"); + pl_button("dynamic##11"); + pl_button("static##11"); + + pl_button("variable##11"); + pl_button("dynamic##11"); + pl_button("static##11"); + + pl_end_collapsing_header(); + } + pl_end_window(); + } +} \ No newline at end of file diff --git a/ui/pl_ui_ext.c b/ui/pl_ui_ext.c new file mode 100644 index 00000000..197eab23 --- /dev/null +++ b/ui/pl_ui_ext.c @@ -0,0 +1,2190 @@ +/* + pl_ui.c +*/ + +/* +Index of this file: +// [SECTION] includes +// [SECTION] context +// [SECTION] enums +// [SECTION] internal api +// [SECTION] stb_text mess +// [SECTION] public api implementations +// [SECTION] internal api implementations +*/ + +//----------------------------------------------------------------------------- +// [SECTION] includes +//----------------------------------------------------------------------------- + +#include "pilotlight.h" +#include "pl_ui_internal.h" +#include // FLT_MAX +#include + +#define PL_MATH_INCLUDE_FUNCTIONS +#include "pl_math.h" +#include "pl_ds.h" + +// extensions +#include "pl_draw_ext.h" + +//----------------------------------------------------------------------------- +// [SECTION] public api implementation +//----------------------------------------------------------------------------- + +void +pl_set_dark_theme(void) +{ + // styles + gptCtx->tStyle.fTitlePadding = 10.0f; + gptCtx->tStyle.fFontSize = 13.0f; + gptCtx->tStyle.fWindowHorizontalPadding = 5.0f; + gptCtx->tStyle.fWindowVerticalPadding = 5.0f; + gptCtx->tStyle.fIndentSize = 15.0f; + gptCtx->tStyle.fScrollbarSize = 10.0f; + gptCtx->tStyle.fSliderSize = 12.0f; + gptCtx->tStyle.tItemSpacing = (plVec2){8.0f, 4.0f}; + gptCtx->tStyle.tInnerSpacing = (plVec2){4.0f, 4.0f}; + gptCtx->tStyle.tFramePadding = (plVec2){4.0f, 4.0f}; + + // colors + gptCtx->tColorScheme.tTitleActiveCol = (plVec4){0.33f, 0.02f, 0.10f, 1.00f}; + gptCtx->tColorScheme.tTitleBgCol = (plVec4){0.04f, 0.04f, 0.04f, 1.00f}; + gptCtx->tColorScheme.tTitleBgCollapsedCol = (plVec4){0.04f, 0.04f, 0.04f, 1.00f}; + gptCtx->tColorScheme.tWindowBgColor = (plVec4){0.10f, 0.10f, 0.10f, 0.78f}; + gptCtx->tColorScheme.tWindowBorderColor = (plVec4){0.33f, 0.02f, 0.10f, 1.00f}; + gptCtx->tColorScheme.tChildBgColor = (plVec4){0.10f, 0.10f, 0.10f, 0.78f}; + gptCtx->tColorScheme.tButtonCol = (plVec4){0.51f, 0.02f, 0.10f, 1.00f}; + gptCtx->tColorScheme.tButtonHoveredCol = (plVec4){0.61f, 0.02f, 0.10f, 1.00f}; + gptCtx->tColorScheme.tButtonActiveCol = (plVec4){0.87f, 0.02f, 0.10f, 1.00f}; + gptCtx->tColorScheme.tTextCol = (plVec4){1.00f, 1.00f, 1.00f, 1.00f}; + gptCtx->tColorScheme.tProgressBarCol = (plVec4){0.90f, 0.70f, 0.00f, 1.00f}; + gptCtx->tColorScheme.tCheckmarkCol = (plVec4){0.87f, 0.02f, 0.10f, 1.00f}; + gptCtx->tColorScheme.tFrameBgCol = (plVec4){0.23f, 0.02f, 0.10f, 1.00f}; + gptCtx->tColorScheme.tFrameBgHoveredCol = (plVec4){0.26f, 0.59f, 0.98f, 0.40f}; + gptCtx->tColorScheme.tFrameBgActiveCol = (plVec4){0.26f, 0.59f, 0.98f, 0.67f}; + gptCtx->tColorScheme.tHeaderCol = (plVec4){0.51f, 0.02f, 0.10f, 1.00f}; + gptCtx->tColorScheme.tHeaderHoveredCol = (plVec4){0.26f, 0.59f, 0.98f, 0.80f}; + gptCtx->tColorScheme.tHeaderActiveCol = (plVec4){0.26f, 0.59f, 0.98f, 1.00f}; + gptCtx->tColorScheme.tScrollbarBgCol = (plVec4){0.05f, 0.05f, 0.05f, 0.85f}; + gptCtx->tColorScheme.tScrollbarHandleCol = (plVec4){0.51f, 0.02f, 0.10f, 1.00f}; + gptCtx->tColorScheme.tScrollbarFrameCol = (plVec4){0.00f, 0.00f, 0.00f, 0.00f}; + gptCtx->tColorScheme.tScrollbarActiveCol = gptCtx->tColorScheme.tButtonActiveCol; + gptCtx->tColorScheme.tScrollbarHoveredCol = gptCtx->tColorScheme.tButtonHoveredCol; +} + +plDrawList2D* +pl_get_draw_list(void) +{ + return gptCtx->ptDrawlist; +} + +plDrawList2D* +pl_get_debug_draw_list(void) +{ + return gptCtx->ptDebugDrawlist; +} + +void +pl_new_frame(void) +{ + + gptIO->bWantTextInput = false; + gptIO->bWantCaptureMouse = false; + gptIO->bWantCaptureKeyboard = false; + + // update state id's from previous frame + gptCtx->uHoveredId = gptCtx->uNextHoveredId; + gptCtx->uNextHoveredId = 0; + gptIO->bWantCaptureKeyboard = gptCtx->uActiveId != 0; + gptIO->bWantCaptureMouse = gptIO->_abMouseOwned[0] || gptCtx->uActiveId != 0 || gptCtx->ptMovingWindow != NULL; + + // null starting state + gptCtx->bActiveIdJustActivated = false; + + // reset previous item data + gptCtx->tPrevItemData.bHovered = false; + + // reset next window data + gptCtx->tNextWindowData.tFlags = PL_NEXT_WINDOW_DATA_FLAGS_NONE; + gptCtx->tNextWindowData.tCollapseCondition = PL_UI_COND_NONE; + gptCtx->tNextWindowData.tPosCondition = PL_UI_COND_NONE; + gptCtx->tNextWindowData.tSizeCondition = PL_UI_COND_NONE; + + // reset active window + if(gptIOI->is_mouse_clicked(PL_MOUSE_BUTTON_LEFT, false) && gptCtx->ptHoveredWindow == NULL) + gptCtx->uActiveWindowId = 0; + + // reset active id if no longer alive + if(gptCtx->uActiveId != 0 && gptCtx->uActiveIdIsAlive != gptCtx->uActiveId) + { + pl__set_active_id(0, NULL); + } + gptCtx->uActiveIdIsAlive = 0; + + if(gptCtx->uActiveWindowId == 0) + gptCtx->ptActiveWindow = NULL; + + // track click ownership + for(uint32_t i = 0; i < 5; i++) + { + if(gptIO->_abMouseClicked[i]) + { + gptIO->_abMouseOwned[i] = (gptCtx->ptHoveredWindow != NULL); + } + } +} + +void +pl__focus_window(plUiWindow* ptWindow) +{ + + pl_sb_del(gptCtx->sbptFocusedWindows, ptWindow->ptRootWindow->uFocusOrder); + for(uint32_t i = 0; i < pl_sb_size(gptCtx->sbptFocusedWindows); i++) + { + gptCtx->sbptFocusedWindows[i]->uFocusOrder = i; + } + ptWindow->ptRootWindow->uFocusOrder = pl_sb_size(gptCtx->sbptFocusedWindows); + pl_sb_push(gptCtx->sbptFocusedWindows, ptWindow->ptRootWindow); +} + +void +pl_end_frame(void) +{ + + const plVec2 tMousePos = gptIOI->get_mouse_pos(); + + // submit windows in display order + pl_sb_reset(gptCtx->sbptWindows); + for(uint32_t i = 0; i < pl_sb_size(gptCtx->sbptFocusedWindows); i++) + { + plUiWindow* ptRootWindow = gptCtx->sbptFocusedWindows[i]; + + if(ptRootWindow->bActive) + pl_submit_window(ptRootWindow); + + // adjust window size if outside viewport + if(ptRootWindow->tPos.x > gptIO->afMainViewportSize[0]) + ptRootWindow->tPos.x = gptIO->afMainViewportSize[0] - ptRootWindow->tSize.x / 2.0f; + + if(ptRootWindow->tPos.y > gptIO->afMainViewportSize[1]) + { + ptRootWindow->tPos.y = gptIO->afMainViewportSize[1] - ptRootWindow->tSize.y / 2.0f; + ptRootWindow->tPos.y = pl_maxf(ptRootWindow->tPos.y, 0.0f); + } + } + + // find windows + gptCtx->ptHoveredWindow = NULL; + gptCtx->ptWheelingWindow = NULL; + if(gptIOI->is_mouse_released(PL_MOUSE_BUTTON_LEFT)) + { + gptCtx->ptMovingWindow = NULL; + gptCtx->ptSizingWindow = NULL; + gptCtx->ptScrollingWindow = NULL; + } + + for(uint32_t i = 0; i < pl_sb_size(gptCtx->sbptWindows); i++) + { + plUiWindow* ptWindow = gptCtx->sbptWindows[i]; + if(pl_rect_contains_point(&ptWindow->tOuterRectClipped, gptIO->_tMousePos)) + { + gptCtx->ptHoveredWindow = ptWindow; + + // scrolling + if(!(ptWindow->tFlags & PL_UI_WINDOW_FLAGS_AUTO_SIZE) && gptIOI->get_mouse_wheel() != 0.0f) + gptCtx->ptWheelingWindow = ptWindow; + } + } + + for(uint32_t i = 0; i < pl_sb_size(gptCtx->sbptWindows); i++) + { + plUiWindow* ptWindow = gptCtx->sbptWindows[i]; + plRect tBoundBox = ptWindow->tOuterRectClipped; + + float fTitleBarHeight = 0.0f; + + if(ptWindow->ptRootWindow == ptWindow) + { + fTitleBarHeight = ptWindow->tTempData.fTitleBarHeight; + + // add padding for resizing from borders + if(!(ptWindow->tFlags & (PL_UI_WINDOW_FLAGS_NO_RESIZE | PL_UI_WINDOW_FLAGS_AUTO_SIZE))) + tBoundBox = pl_rect_expand(&tBoundBox, 2.0f); + } + + if(gptIOI->is_mouse_hovering_rect(tBoundBox.tMin, tBoundBox.tMax)) + { + + // check if window is activated + if(gptIOI->is_mouse_clicked(PL_MOUSE_BUTTON_LEFT, false) && gptCtx->ptHoveredWindow == ptWindow) + { + gptCtx->ptMovingWindow = NULL; + gptCtx->uActiveWindowId = ptWindow->ptRootWindow->uId; + gptCtx->ptActiveWindow = ptWindow; + + gptIO->_abMouseOwned[PL_MOUSE_BUTTON_LEFT] = true; + + const plRect tTitleBarHitRegion = { + .tMin = {ptWindow->tPos.x + 2.0f, ptWindow->tPos.y + 2.0f}, + .tMax = {ptWindow->tPos.x + ptWindow->tSize.x - 2.0f, ptWindow->tPos.y + fTitleBarHeight} + }; + + // check if window titlebar is clicked + if(!(ptWindow->tFlags & PL_UI_WINDOW_FLAGS_NO_TITLE_BAR) && gptIOI->is_mouse_hovering_rect(tTitleBarHitRegion.tMin, tTitleBarHitRegion.tMax)) + gptCtx->ptMovingWindow = ptWindow; + + pl__focus_window(ptWindow); + } + } + } + + // scroll window + if(gptCtx->ptWheelingWindow) + { + gptCtx->ptWheelingWindow->tScroll.y -= gptIOI->get_mouse_wheel() * 10.0f; + gptCtx->ptWheelingWindow->tScroll.y = pl_clampf(0.0f, gptCtx->ptWheelingWindow->tScroll.y, gptCtx->ptWheelingWindow->tScrollMax.y); + } + + // moving window + if(gptCtx->ptMovingWindow && gptIOI->is_mouse_dragging(PL_MOUSE_BUTTON_LEFT, 2.0f) && !(gptCtx->ptMovingWindow->tFlags & PL_UI_WINDOW_FLAGS_NO_MOVE)) + { + + if(tMousePos.x > 0.0f && tMousePos.x < gptIO->afMainViewportSize[0]) + gptCtx->ptMovingWindow->tPos.x = gptCtx->ptMovingWindow->tPos.x + gptIOI->get_mouse_drag_delta(PL_MOUSE_BUTTON_LEFT, 2.0f).x; + + if(tMousePos.y > 0.0f && tMousePos.y < gptIO->afMainViewportSize[1]) + gptCtx->ptMovingWindow->tPos.y = gptCtx->ptMovingWindow->tPos.y + gptIOI->get_mouse_drag_delta(PL_MOUSE_BUTTON_LEFT, 2.0f).y; + + // clamp x + gptCtx->ptMovingWindow->tPos.x = pl_maxf(gptCtx->ptMovingWindow->tPos.x, -gptCtx->ptMovingWindow->tSize.x / 2.0f); + gptCtx->ptMovingWindow->tPos.x = pl_minf(gptCtx->ptMovingWindow->tPos.x, gptIO->afMainViewportSize[0] - gptCtx->ptMovingWindow->tSize.x / 2.0f); + + // clamp y + gptCtx->ptMovingWindow->tPos.y = pl_maxf(gptCtx->ptMovingWindow->tPos.y, 0.0f); + gptCtx->ptMovingWindow->tPos.y = pl_minf(gptCtx->ptMovingWindow->tPos.y, gptIO->afMainViewportSize[1] - 50.0f); + + gptIOI->reset_mouse_drag_delta(PL_MOUSE_BUTTON_LEFT); + } + + gptIO->_fMouseWheel = 0.0f; + gptIO->_fMouseWheelH = 0.0f; + pl_sb_reset(gptIO->_sbInputQueueCharacters); +} + +void +pl_render(plRenderEncoder tEncoder, float fWidth, float fHeight, uint32_t uMSAASampleCount) +{ + gptDraw->submit_2d_layer(gptCtx->ptBgLayer); + for(uint32_t i = 0; i < pl_sb_size(gptCtx->sbptWindows); i++) + { + if(gptCtx->sbptWindows[i]->uHideFrames == 0) + { + gptDraw->submit_2d_layer(gptCtx->sbptWindows[i]->ptBgLayer); + gptDraw->submit_2d_layer(gptCtx->sbptWindows[i]->ptFgLayer); + } + else + { + gptCtx->sbptWindows[i]->uHideFrames--; + } + } + gptDraw->submit_2d_layer(gptCtx->tTooltipWindow.ptBgLayer); + gptDraw->submit_2d_layer(gptCtx->tTooltipWindow.ptFgLayer); + gptDraw->submit_2d_layer(gptCtx->ptFgLayer); + gptDraw->submit_2d_layer(gptCtx->ptDebugLayer); + + gptDraw->submit_2d_drawlist(gptCtx->ptDrawlist, tEncoder, fWidth, fHeight, uMSAASampleCount); + gptDraw->submit_2d_drawlist(gptCtx->ptDebugDrawlist, tEncoder, fWidth, fHeight, uMSAASampleCount); + + pl_end_frame(); +} + +void +pl_push_theme_color(plUiColor tColor, const plVec4* ptColor) +{ + const plUiColorStackItem tPrevItem = { + .tIndex = tColor, + .tColor = gptCtx->tColorScheme.atColors[tColor] + }; + gptCtx->tColorScheme.atColors[tColor] = *ptColor; + pl_sb_push(gptCtx->sbtColorStack, tPrevItem); +} + +void +pl_pop_theme_color(uint32_t uCount) +{ + for(uint32_t i = 0; i < uCount; i++) + { + const plUiColorStackItem tPrevItem = pl_sb_last(gptCtx->sbtColorStack); + gptCtx->tColorScheme.atColors[tPrevItem.tIndex] = tPrevItem.tColor; + pl_sb_pop(gptCtx->sbtColorStack); + } +} + +void +pl_set_default_font(plFont* ptFont) +{ + gptCtx->ptFont = ptFont; +} + +plFont* +pl_get_default_font(void) +{ + return gptCtx->ptFont; +} + +void +pl_layout_row(plUiLayoutRowType tType, float fHeight, uint32_t uWidgetCount, const float* pfSizesOrRatios) +{ + plUiWindow* ptWindow = gptCtx->ptCurrentWindow; + + plUiLayoutRow tNewRow = { + .fHeight = fHeight, + .fSpecifiedHeight = fHeight, + .tType = tType, + .tSystemType = PL_UI_LAYOUT_SYSTEM_TYPE_ARRAY, + .uColumns = uWidgetCount, + .pfSizesOrRatios = pfSizesOrRatios + }; + ptWindow->tTempData.tCurrentLayoutRow = tNewRow; +} + +void +pl_end_window(void) +{ + plUiWindow* ptWindow = gptCtx->ptCurrentWindow; + + float fTitleBarHeight = ptWindow->tTempData.fTitleBarHeight; + + // set content sized based on last frames maximum cursor position + if(ptWindow->bVisible) + { + // cursor max pos - start pos + padding + ptWindow->tContentSize = pl_add_vec2( + (plVec2){gptCtx->tStyle.fWindowHorizontalPadding, gptCtx->tStyle.fWindowVerticalPadding}, + pl_sub_vec2(ptWindow->tTempData.tCursorMaxPos, ptWindow->tTempData.tCursorStartPos) + + ); + } + ptWindow->tScrollMax = pl_sub_vec2(ptWindow->tContentSize, (plVec2){ptWindow->tSize.x, ptWindow->tSize.y - fTitleBarHeight}); + + // clamp scrolling max + ptWindow->tScrollMax = pl_max_vec2(ptWindow->tScrollMax, (plVec2){0}); + ptWindow->bScrollbarX = ptWindow->tScrollMax.x > 0.0f; + ptWindow->bScrollbarY = ptWindow->tScrollMax.y > 0.0f; + + if(ptWindow->bScrollbarX && ptWindow->bScrollbarY) + { + ptWindow->tScrollMax.y += gptCtx->tStyle.fScrollbarSize + 2.0f; + ptWindow->tScrollMax.x += gptCtx->tStyle.fScrollbarSize + 2.0f; + } + else if(!ptWindow->bScrollbarY) + ptWindow->tScroll.y = 0; + else if(!ptWindow->bScrollbarX) + ptWindow->tScroll.x = 0; + + const bool bScrollBarsPresent = ptWindow->bScrollbarX || ptWindow->bScrollbarY; + + // clamp window size to min/max + ptWindow->tSize = pl_clamp_vec2(ptWindow->tMinSize, ptWindow->tSize, ptWindow->tMaxSize); + + // autosized non collapsed + if(ptWindow->tFlags & PL_UI_WINDOW_FLAGS_AUTO_SIZE && !ptWindow->bCollapsed) + { + + const plRect tBgRect = pl_calculate_rect( + (plVec2){ptWindow->tPos.x, ptWindow->tPos.y + fTitleBarHeight}, + (plVec2){ptWindow->tSize.x, ptWindow->tSize.y - fTitleBarHeight}); + + // ensure window doesn't get too small + ptWindow->tSize.x = ptWindow->tContentSize.x + gptCtx->tStyle.fWindowHorizontalPadding * 2.0f; + ptWindow->tSize.y = fTitleBarHeight + ptWindow->tContentSize.y + gptCtx->tStyle.fWindowVerticalPadding; + + // clamp window size to min/max + ptWindow->tSize = pl_clamp_vec2(ptWindow->tMinSize, ptWindow->tSize, ptWindow->tMaxSize); + + ptWindow->tOuterRect = pl_calculate_rect(ptWindow->tPos, ptWindow->tSize); + ptWindow->tOuterRectClipped = ptWindow->tOuterRect; + + // remove scissor rect + gptDraw->pop_clip_rect(gptCtx->ptDrawlist); + + // draw background + gptDraw->add_rect_filled(ptWindow->ptBgLayer, tBgRect.tMin, tBgRect.tMax, gptCtx->tColorScheme.tWindowBgColor); + + ptWindow->tFullSize = ptWindow->tSize; + } + + // regular window non collapsed + else if(!ptWindow->bCollapsed) + { + plUiWindow* ptParentWindow = ptWindow->ptParentWindow; + + plRect tBgRect = pl_calculate_rect( + (plVec2){ptWindow->tPos.x, ptWindow->tPos.y + fTitleBarHeight}, + (plVec2){ptWindow->tSize.x, ptWindow->tSize.y - fTitleBarHeight}); + + plRect tParentBgRect = ptParentWindow->tOuterRect; + + if(ptWindow->tFlags & PL_UI_WINDOW_FLAGS_CHILD_WINDOW) + { + tBgRect = pl_rect_clip(&tBgRect, &tParentBgRect); + } + + gptDraw->pop_clip_rect(gptCtx->ptDrawlist); + + const uint32_t uResizeHash = ptWindow->uId + 1; + const uint32_t uWestResizeHash = uResizeHash + 1; + const uint32_t uEastResizeHash = uResizeHash + 2; + const uint32_t uNorthResizeHash = uResizeHash + 3; + const uint32_t uSouthResizeHash = uResizeHash + 4; + const uint32_t uVerticalScrollHash = uResizeHash + 5; + const uint32_t uHorizonatalScrollHash = uResizeHash + 6; + const float fRightSidePadding = ptWindow->bScrollbarY ? gptCtx->tStyle.fScrollbarSize + 2.0f : 0.0f; + const float fBottomPadding = ptWindow->bScrollbarX ? gptCtx->tStyle.fScrollbarSize + 2.0f : 0.0f; + const float fHoverPadding = 4.0f; + + // draw background + gptDraw->add_rect_filled(ptWindow->ptBgLayer, tBgRect.tMin, tBgRect.tMax, gptCtx->tColorScheme.tWindowBgColor); + + // vertical scroll bar + if(ptWindow->bScrollbarY) + pl_render_scrollbar(ptWindow, uVerticalScrollHash, PL_UI_AXIS_Y); + + // horizontal scroll bar + if(ptWindow->bScrollbarX) + pl_render_scrollbar(ptWindow, uHorizonatalScrollHash, PL_UI_AXIS_X); + + const plVec2 tTopLeft = pl_rect_top_left(&ptWindow->tOuterRect); + const plVec2 tBottomLeft = pl_rect_bottom_left(&ptWindow->tOuterRect); + const plVec2 tTopRight = pl_rect_top_right(&ptWindow->tOuterRect); + const plVec2 tBottomRight = pl_rect_bottom_right(&ptWindow->tOuterRect); + + // resizing grip + if (!(ptWindow->tFlags & PL_UI_WINDOW_FLAGS_NO_RESIZE)) + { + { + const plVec2 tCornerTopLeftPos = pl_add_vec2(tBottomRight, (plVec2){-15.0f, -15.0f}); + const plVec2 tCornerTopPos = pl_add_vec2(tBottomRight, (plVec2){0.0f, -15.0f}); + const plVec2 tCornerLeftPos = pl_add_vec2(tBottomRight, (plVec2){-15.0f, 0.0f}); + + const plRect tBoundingBox = pl_calculate_rect(tCornerTopLeftPos, (plVec2){15.0f, 15.0f}); + bool bHovered = false; + bool bHeld = false; + const bool bPressed = pl_button_behavior(&tBoundingBox, uResizeHash, &bHovered, &bHeld); + + if(gptCtx->uActiveId == uResizeHash) + { + gptDraw->add_triangle_filled(ptWindow->ptFgLayer, tBottomRight, tCornerTopPos, tCornerLeftPos, (plVec4){0.99f, 0.02f, 0.10f, 1.0f}); + gptIOI->set_mouse_cursor(PL_MOUSE_CURSOR_RESIZE_NWSE); + } + else if(gptCtx->uHoveredId == uResizeHash) + { + gptDraw->add_triangle_filled(ptWindow->ptFgLayer, tBottomRight, tCornerTopPos, tCornerLeftPos, (plVec4){0.66f, 0.02f, 0.10f, 1.0f}); + gptIOI->set_mouse_cursor(PL_MOUSE_CURSOR_RESIZE_NWSE); + } + else + { + gptDraw->add_triangle_filled(ptWindow->ptFgLayer, tBottomRight, tCornerTopPos, tCornerLeftPos, (plVec4){0.33f, 0.02f, 0.10f, 1.0f}); + } + } + + // east border + { + + plRect tBoundingBox = pl_calculate_rect(tTopRight, (plVec2){0.0f, ptWindow->tSize.y - 15.0f}); + tBoundingBox = pl_rect_expand_vec2(&tBoundingBox, (plVec2){fHoverPadding / 2.0f, 0.0f}); + + bool bHovered = false; + bool bHeld = false; + const bool bPressed = pl_button_behavior(&tBoundingBox, uEastResizeHash, &bHovered, &bHeld); + + if(gptCtx->uActiveId == uEastResizeHash) + { + gptDraw->add_line(ptWindow->ptFgLayer, tTopRight, tBottomRight, (plVec4){0.99f, 0.02f, 0.10f, 1.0f}, 2.0f); + gptIOI->set_mouse_cursor(PL_MOUSE_CURSOR_RESIZE_EW); + } + else if(gptCtx->uHoveredId == uEastResizeHash) + { + gptDraw->add_line(ptWindow->ptFgLayer, tTopRight, tBottomRight, (plVec4){0.66f, 0.02f, 0.10f, 1.0f}, 2.0f); + gptIOI->set_mouse_cursor(PL_MOUSE_CURSOR_RESIZE_EW); + } + } + + // west border + { + plRect tBoundingBox = pl_calculate_rect(tTopLeft, (plVec2){0.0f, ptWindow->tSize.y - 15.0f}); + tBoundingBox = pl_rect_expand_vec2(&tBoundingBox, (plVec2){fHoverPadding / 2.0f, 0.0f}); + + bool bHovered = false; + bool bHeld = false; + const bool bPressed = pl_button_behavior(&tBoundingBox, uWestResizeHash, &bHovered, &bHeld); + + if(gptCtx->uActiveId == uWestResizeHash) + { + gptDraw->add_line(ptWindow->ptFgLayer, tTopLeft, tBottomLeft, (plVec4){0.99f, 0.02f, 0.10f, 1.0f}, 2.0f); + gptIOI->set_mouse_cursor(PL_MOUSE_CURSOR_RESIZE_EW); + } + else if(gptCtx->uHoveredId == uWestResizeHash) + { + gptDraw->add_line(ptWindow->ptFgLayer, tTopLeft, tBottomLeft, (plVec4){0.66f, 0.02f, 0.10f, 1.0f}, 2.0f); + gptIOI->set_mouse_cursor(PL_MOUSE_CURSOR_RESIZE_EW); + } + } + + // north border + { + plRect tBoundingBox = {tTopLeft, (plVec2){tTopRight.x - 15.0f, tTopRight.y}}; + tBoundingBox = pl_rect_expand_vec2(&tBoundingBox, (plVec2){0.0f, fHoverPadding / 2.0f}); + + bool bHovered = false; + bool bHeld = false; + const bool bPressed = pl_button_behavior(&tBoundingBox, uNorthResizeHash, &bHovered, &bHeld); + + if(gptCtx->uActiveId == uNorthResizeHash) + { + gptDraw->add_line(ptWindow->ptFgLayer, tTopLeft, tTopRight, (plVec4){0.99f, 0.02f, 0.10f, 1.0f}, 2.0f); + gptIOI->set_mouse_cursor(PL_MOUSE_CURSOR_RESIZE_NS); + } + else if(gptCtx->uHoveredId == uNorthResizeHash) + { + gptDraw->add_line(ptWindow->ptFgLayer, tTopLeft, tTopRight, (plVec4){0.66f, 0.02f, 0.10f, 1.0f}, 2.0f); + gptIOI->set_mouse_cursor(PL_MOUSE_CURSOR_RESIZE_NS); + } + } + + // south border + { + plRect tBoundingBox = {tBottomLeft, (plVec2){tBottomRight.x - 15.0f, tBottomRight.y}}; + tBoundingBox = pl_rect_expand_vec2(&tBoundingBox, (plVec2){0.0f, fHoverPadding / 2.0f}); + + bool bHovered = false; + bool bHeld = false; + const bool bPressed = pl_button_behavior(&tBoundingBox, uSouthResizeHash, &bHovered, &bHeld); + + if(gptCtx->uActiveId == uSouthResizeHash) + { + gptDraw->add_line(ptWindow->ptFgLayer, tBottomLeft, tBottomRight, (plVec4){0.99f, 0.02f, 0.10f, 1.0f}, 2.0f); + gptIOI->set_mouse_cursor(PL_MOUSE_CURSOR_RESIZE_NS); + } + else if(gptCtx->uHoveredId == uSouthResizeHash) + { + gptDraw->add_line(ptWindow->ptFgLayer, tBottomLeft, tBottomRight, (plVec4){0.66f, 0.02f, 0.10f, 1.0f}, 2.0f); + gptIOI->set_mouse_cursor(PL_MOUSE_CURSOR_RESIZE_NS); + } + } + } + + // draw border + gptDraw->add_rect(ptWindow->ptFgLayer, ptWindow->tOuterRect.tMin, ptWindow->tOuterRect.tMax, gptCtx->tColorScheme.tWindowBorderColor, 1.0f); + + // handle corner resizing + if(gptIOI->is_mouse_dragging(PL_MOUSE_BUTTON_LEFT, 2.0f)) + { + const plVec2 tMousePos = gptIOI->get_mouse_pos(); + + if(gptCtx->uActiveId == uResizeHash) + { + gptCtx->ptSizingWindow = ptWindow; + ptWindow->tSize = pl_sub_vec2(tMousePos, ptWindow->tPos); + ptWindow->tSize = pl_max_vec2(ptWindow->tSize, ptWindow->tMinSize); + ptWindow->tScroll = pl_clamp_vec2((plVec2){0}, ptWindow->tScroll, ptWindow->tScrollMax); + } + + // handle east resizing + else if(gptCtx->uActiveId == uEastResizeHash) + { + gptCtx->ptSizingWindow = ptWindow; + ptWindow->tSize.x = tMousePos.x - ptWindow->tPos.x; + ptWindow->tSize = pl_max_vec2(ptWindow->tSize, ptWindow->tMinSize); + ptWindow->tScroll = pl_clamp_vec2((plVec2){0}, ptWindow->tScroll, ptWindow->tScrollMax); + } + + // handle west resizing + else if(gptCtx->uActiveId == uWestResizeHash) + { + gptCtx->ptSizingWindow = ptWindow; + ptWindow->tSize.x = tTopRight.x - tMousePos.x; + ptWindow->tSize = pl_max_vec2(ptWindow->tSize, ptWindow->tMinSize); + ptWindow->tPos.x = tTopRight.x - ptWindow->tSize.x; + ptWindow->tScroll = pl_clamp_vec2((plVec2){0}, ptWindow->tScroll, ptWindow->tScrollMax); + } + + // handle north resizing + else if(gptCtx->uActiveId == uNorthResizeHash) + { + gptCtx->ptSizingWindow = ptWindow; + ptWindow->tSize.y = tBottomRight.y - tMousePos.y; + ptWindow->tSize = pl_max_vec2(ptWindow->tSize, ptWindow->tMinSize); + ptWindow->tPos.y = tBottomRight.y - ptWindow->tSize.y; + ptWindow->tScroll = pl_clamp_vec2((plVec2){0}, ptWindow->tScroll, ptWindow->tScrollMax); + } + + // handle south resizing + else if(gptCtx->uActiveId == uSouthResizeHash) + { + gptCtx->ptSizingWindow = ptWindow; + ptWindow->tSize.y = tMousePos.y - ptWindow->tPos.y; + ptWindow->tSize = pl_max_vec2(ptWindow->tSize, ptWindow->tMinSize); + ptWindow->tScroll = pl_clamp_vec2((plVec2){0}, ptWindow->tScroll, ptWindow->tScrollMax); + } + + // handle vertical scrolling with scroll bar + else if(gptCtx->uActiveId == uVerticalScrollHash) + { + gptCtx->ptScrollingWindow = ptWindow; + + if(tMousePos.y > ptWindow->tPos.y && tMousePos.y < ptWindow->tPos.y + ptWindow->tSize.y) + { + const float fScrollConversion = roundf(ptWindow->tContentSize.y / ptWindow->tSize.y); + ptWindow->tScroll.y += gptIOI->get_mouse_drag_delta(PL_MOUSE_BUTTON_LEFT, 1.0f).y * fScrollConversion; + ptWindow->tScroll.y = pl_clampf(0.0f, ptWindow->tScroll.y, ptWindow->tScrollMax.y); + gptIOI->reset_mouse_drag_delta(PL_MOUSE_BUTTON_LEFT); + } + } + + // handle horizontal scrolling with scroll bar + else if(gptCtx->uActiveId == uHorizonatalScrollHash) + { + gptCtx->ptScrollingWindow = ptWindow; + + if(tMousePos.x > ptWindow->tPos.x && tMousePos.x < ptWindow->tPos.x + ptWindow->tSize.x) + { + const float fScrollConversion = roundf(ptWindow->tContentSize.x / ptWindow->tSize.x); + ptWindow->tScroll.x += gptIOI->get_mouse_drag_delta(PL_MOUSE_BUTTON_LEFT, 1.0f).x * fScrollConversion; + ptWindow->tScroll.x = pl_clampf(0.0f, ptWindow->tScroll.x, ptWindow->tScrollMax.x); + gptIOI->reset_mouse_drag_delta(PL_MOUSE_BUTTON_LEFT); + } + } + } + gptCtx->ptCurrentWindow->tFullSize = ptWindow->tSize; + + if(gptCtx->uActiveId >= uResizeHash && gptCtx->uActiveId < uResizeHash + 7 && gptIOI->is_mouse_down(PL_MOUSE_BUTTON_LEFT)) + pl__set_active_id(gptCtx->uActiveId, ptWindow); + } + + gptCtx->ptCurrentWindow = NULL; + pl_sb_pop(gptCtx->sbuIdStack); +} + +bool +pl_begin_window(const char* pcName, bool* pbOpen, plUiWindowFlags tFlags) +{ + bool bResult = pl_begin_window_ex(pcName, pbOpen, tFlags); + + static const float pfRatios[] = {300.0f}; + if(bResult) + { + pl_layout_row(PL_UI_LAYOUT_ROW_TYPE_STATIC, 0.0f, 1, pfRatios); + } + else + pl_end_window(); + return bResult; +} + +plDrawLayer2D* +pl_get_window_fg_drawlayer(void) +{ + PL_ASSERT(gptCtx->ptCurrentWindow && "no current window"); + plUiWindow* ptWindow = gptCtx->ptCurrentWindow; + return ptWindow->ptFgLayer; +} + +plDrawLayer2D* +pl_get_window_bg_drawlayer(void) +{ + PL_ASSERT(gptCtx->ptCurrentWindow && "no current window"); + plUiWindow* ptWindow = gptCtx->ptCurrentWindow; + return ptWindow->ptBgLayer; +} + +plVec2 +pl_get_cursor_pos(void) +{ + return pl__ui_get_cursor_pos(); +} + +void +pl_set_next_window_size(plVec2 tSize, plUiConditionFlags tCondition) +{ + gptCtx->tNextWindowData.tSize = tSize; + gptCtx->tNextWindowData.tFlags |= PL_NEXT_WINDOW_DATA_FLAGS_HAS_SIZE; + gptCtx->tNextWindowData.tSizeCondition = tCondition; +} + +void +pl_end_child(void) +{ + plUiWindow* ptWindow = gptCtx->ptCurrentWindow; + plUiWindow* ptParentWindow = ptWindow->ptParentWindow; + + // set content sized based on last frames maximum cursor position + if(ptWindow->bVisible) + { + ptWindow->tContentSize = pl_add_vec2( + (plVec2){gptCtx->tStyle.fWindowHorizontalPadding, gptCtx->tStyle.fWindowVerticalPadding}, + pl_sub_vec2(ptWindow->tTempData.tCursorMaxPos, ptWindow->tTempData.tCursorStartPos) + + ); + } + ptWindow->tScrollMax = pl_sub_vec2(ptWindow->tContentSize, ptWindow->tSize); + + // clamp scrolling max + ptWindow->tScrollMax = pl_max_vec2(ptWindow->tScrollMax, (plVec2){0}); + ptWindow->bScrollbarX = ptWindow->tScrollMax.x > 0.0f; + ptWindow->bScrollbarY = ptWindow->tScrollMax.y > 0.0f; + + if(ptWindow->bScrollbarX && ptWindow->bScrollbarY) + { + ptWindow->tScrollMax.y += gptCtx->tStyle.fScrollbarSize + 2.0f; + ptWindow->tScrollMax.x += gptCtx->tStyle.fScrollbarSize + 2.0f; + } + + // clamp window size to min/max + ptWindow->tSize = pl_clamp_vec2(ptWindow->tMinSize, ptWindow->tSize, ptWindow->tMaxSize); + + plRect tParentBgRect = ptParentWindow->tOuterRect; + const plRect tBgRect = pl_rect_clip(&ptWindow->tOuterRect, &tParentBgRect); + + gptDraw->pop_clip_rect(gptCtx->ptDrawlist); + + const uint32_t uVerticalScrollHash = pl_str_hash("##scrollright", 0, pl_sb_top(gptCtx->sbuIdStack)); + const uint32_t uHorizonatalScrollHash = pl_str_hash("##scrollbottom", 0, pl_sb_top(gptCtx->sbuIdStack)); + + // draw background + gptDraw->add_rect_filled(ptParentWindow->ptBgLayer, tBgRect.tMin, tBgRect.tMax, gptCtx->tColorScheme.tWindowBgColor); + + // vertical scroll bar + if(ptWindow->bScrollbarY) + pl_render_scrollbar(ptWindow, uVerticalScrollHash, PL_UI_AXIS_Y); + + // horizontal scroll bar + if(ptWindow->bScrollbarX) + pl_render_scrollbar(ptWindow, uHorizonatalScrollHash, PL_UI_AXIS_X); + + // handle vertical scrolling with scroll bar + if(gptCtx->uActiveId == uVerticalScrollHash && gptIOI->is_mouse_dragging(PL_MOUSE_BUTTON_LEFT, 2.0f)) + { + const float fScrollConversion = roundf(ptWindow->tContentSize.y / ptWindow->tSize.y); + gptCtx->ptScrollingWindow = ptWindow; + gptCtx->uNextHoveredId = uVerticalScrollHash; + ptWindow->tScroll.y += gptIOI->get_mouse_drag_delta(PL_MOUSE_BUTTON_LEFT, 1.0f).y * fScrollConversion; + ptWindow->tScroll.y = pl_clampf(0.0f, ptWindow->tScroll.y, ptWindow->tScrollMax.y); + gptIOI->reset_mouse_drag_delta(PL_MOUSE_BUTTON_LEFT); + } + + // handle horizontal scrolling with scroll bar + else if(gptCtx->uActiveId == uHorizonatalScrollHash && gptIOI->is_mouse_dragging(PL_MOUSE_BUTTON_LEFT, 2.0f)) + { + const float fScrollConversion = roundf(ptWindow->tContentSize.x / ptWindow->tSize.x); + gptCtx->ptScrollingWindow = ptWindow; + gptCtx->uNextHoveredId = uHorizonatalScrollHash; + ptWindow->tScroll.x += gptIOI->get_mouse_drag_delta(PL_MOUSE_BUTTON_LEFT, 1.0f).x * fScrollConversion; + ptWindow->tScroll.x = pl_clampf(0.0f, ptWindow->tScroll.x, ptWindow->tScrollMax.x); + gptIOI->reset_mouse_drag_delta(PL_MOUSE_BUTTON_LEFT); + } + + if((gptCtx->uActiveId == uHorizonatalScrollHash || gptCtx->uActiveId == uVerticalScrollHash) && gptIOI->is_mouse_down(PL_MOUSE_BUTTON_LEFT)) + pl__set_active_id(gptCtx->uActiveId, ptWindow); + + ptWindow->tFullSize = ptWindow->tSize; + pl_sb_pop(gptCtx->sbuIdStack); + gptCtx->ptCurrentWindow = ptParentWindow; + + pl_advance_cursor(ptWindow->tSize.x, ptWindow->tSize.y); +} + +bool +pl_begin_child(const char* pcName) +{ + plUiWindow* ptParentWindow = gptCtx->ptCurrentWindow; + plUiLayoutRow* ptCurrentRow = &ptParentWindow->tTempData.tCurrentLayoutRow; + const plVec2 tWidgetSize = pl_calculate_item_size(200.0f); + + const plUiWindowFlags tFlags = + PL_UI_WINDOW_FLAGS_CHILD_WINDOW | + PL_UI_WINDOW_FLAGS_NO_TITLE_BAR | + PL_UI_WINDOW_FLAGS_NO_RESIZE | + PL_UI_WINDOW_FLAGS_NO_COLLAPSE | + PL_UI_WINDOW_FLAGS_NO_MOVE; + + pl_set_next_window_size(tWidgetSize, PL_UI_COND_ALWAYS); + bool bValue = pl_begin_window_ex(pcName, NULL, tFlags); + + static const float pfRatios[] = {300.0f}; + if(bValue) + { + plUiWindow* ptWindow = gptCtx->ptCurrentWindow; + ptWindow->tMinSize = pl_min_vec2(ptWindow->tMinSize, tWidgetSize); + pl_layout_row(PL_UI_LAYOUT_ROW_TYPE_STATIC, 0.0f, 1, pfRatios); + } + else + { + pl_end_child(); + } + return bValue; +} + +void +pl_begin_tooltip(void) +{ + plUiWindow* ptWindow = &gptCtx->tTooltipWindow; + + ptWindow->tContentSize = pl_add_vec2( + (plVec2){gptCtx->tStyle.fWindowHorizontalPadding, gptCtx->tStyle.fWindowVerticalPadding}, + pl_sub_vec2( + ptWindow->tTempData.tCursorMaxPos, + ptWindow->tTempData.tCursorStartPos) + ); + + memset(&ptWindow->tTempData, 0, sizeof(plUiTempWindowData)); + + ptWindow->tFlags |= + PL_UI_WINDOW_FLAGS_TOOLTIP | + PL_UI_WINDOW_FLAGS_NO_TITLE_BAR | + PL_UI_WINDOW_FLAGS_NO_RESIZE | + PL_UI_WINDOW_FLAGS_NO_COLLAPSE | + PL_UI_WINDOW_FLAGS_AUTO_SIZE | + PL_UI_WINDOW_FLAGS_NO_MOVE; + + // place window at mouse position + const plVec2 tMousePos = gptIOI->get_mouse_pos(); + ptWindow->tTempData.tCursorStartPos = pl_add_vec2(tMousePos, (plVec2){gptCtx->tStyle.fWindowHorizontalPadding, 0.0f}); + ptWindow->tPos = tMousePos; + ptWindow->tTempData.tRowPos.x = floorf(gptCtx->tStyle.fWindowHorizontalPadding + tMousePos.x); + ptWindow->tTempData.tRowPos.y = floorf(gptCtx->tStyle.fWindowVerticalPadding + tMousePos.y); + + const plVec2 tStartClip = { ptWindow->tPos.x, ptWindow->tPos.y }; + const plVec2 tEndClip = { ptWindow->tSize.x, ptWindow->tSize.y }; + gptDraw->push_clip_rect(gptCtx->ptDrawlist, pl_calculate_rect(tStartClip, tEndClip), false); + + ptWindow->ptParentWindow = gptCtx->ptCurrentWindow; + gptCtx->ptCurrentWindow = ptWindow; + + static const float pfRatios[] = {300.0f}; + pl_layout_row(PL_UI_LAYOUT_ROW_TYPE_STATIC, 0.0f, 1, pfRatios); +} + +void +pl_end_tooltip(void) +{ + plUiWindow* ptWindow = &gptCtx->tTooltipWindow; + + ptWindow->tSize.x = ptWindow->tContentSize.x + gptCtx->tStyle.fWindowHorizontalPadding; + ptWindow->tSize.y = ptWindow->tContentSize.y; + + gptDraw->add_rect_filled(ptWindow->ptBgLayer, + ptWindow->tPos, + pl_add_vec2(ptWindow->tPos, ptWindow->tSize), gptCtx->tColorScheme.tWindowBgColor); + + gptDraw->pop_clip_rect(gptCtx->ptDrawlist); + gptCtx->ptCurrentWindow = ptWindow->ptParentWindow; +} + +plVec2 +pl_get_window_pos(void) +{ + plUiWindow* ptWindow = gptCtx->ptCurrentWindow; + return ptWindow->tPos; +} + +plVec2 +pl_get_window_size(void) +{ + plUiWindow* ptWindow = gptCtx->ptCurrentWindow; + return ptWindow->tSize; +} + +plVec2 +pl_get_window_scroll(void) +{ + plUiWindow* ptWindow = gptCtx->ptCurrentWindow; + return ptWindow->tScroll; +} + +plVec2 +pl_get_window_scroll_max(void) +{ + plUiWindow* ptWindow = gptCtx->ptCurrentWindow; + return ptWindow->tScrollMax; +} + +void +pl_set_window_scroll(plVec2 tScroll) +{ + plUiWindow* ptWindow = gptCtx->ptCurrentWindow; + if(ptWindow->tScrollMax.x >= tScroll.x) + ptWindow->tScroll.x = tScroll.x; + + if(ptWindow->tScrollMax.y >= tScroll.y) + ptWindow->tScroll.y = tScroll.y; +} + +void +pl_set_next_window_pos(plVec2 tPos, plUiConditionFlags tCondition) +{ + gptCtx->tNextWindowData.tPos = tPos; + gptCtx->tNextWindowData.tFlags |= PL_NEXT_WINDOW_DATA_FLAGS_HAS_POS; + gptCtx->tNextWindowData.tPosCondition = tCondition; +} + +void +pl_set_next_window_collapse(bool bCollapsed, plUiConditionFlags tCondition) +{ + gptCtx->tNextWindowData.bCollapsed = bCollapsed; + gptCtx->tNextWindowData.tFlags |= PL_NEXT_WINDOW_DATA_FLAGS_HAS_COLLAPSED; + gptCtx->tNextWindowData.tCollapseCondition = tCondition; +} + +bool +pl_step_clipper(plUiClipper* ptClipper) +{ + if(ptClipper->uItemCount == 0) + return false; + + if(ptClipper->uDisplayStart == 0 && ptClipper->uDisplayEnd == 0) + { + ptClipper->uDisplayStart = 0; + ptClipper->uDisplayEnd = 1; + ptClipper->_fItemHeight = 0.0f; + ptClipper->_fStartPosY = pl__ui_get_cursor_pos().y; + return true; + } + else if (ptClipper->_fItemHeight == 0.0f) + { + ptClipper->_fItemHeight = pl__ui_get_cursor_pos().y - ptClipper->_fStartPosY; + if(ptClipper->_fStartPosY < pl_get_window_pos().y) + ptClipper->uDisplayStart = (uint32_t)((pl_get_window_pos().y - ptClipper->_fStartPosY) / ptClipper->_fItemHeight); + ptClipper->uDisplayEnd = ptClipper->uDisplayStart + (uint32_t)(pl_get_window_size().y / ptClipper->_fItemHeight) + 1; + ptClipper->uDisplayEnd = pl_minu(ptClipper->uDisplayEnd, ptClipper->uItemCount) + 1; + if(ptClipper->uDisplayStart > 0) + ptClipper->uDisplayStart--; + + if(ptClipper->uDisplayEnd > ptClipper->uItemCount) + ptClipper->uDisplayEnd = ptClipper->uItemCount; + + if(ptClipper->uDisplayStart > 0) + { + for(uint32_t i = 0; i < gptCtx->ptCurrentWindow->tTempData.tCurrentLayoutRow.uColumns; i++) + pl_advance_cursor(0.0f, (float)ptClipper->uDisplayStart * ptClipper->_fItemHeight); + } + ptClipper->uDisplayStart++; + return true; + } + else + { + if(ptClipper->uDisplayEnd < ptClipper->uItemCount) + { + for(uint32_t i = 0; i < gptCtx->ptCurrentWindow->tTempData.tCurrentLayoutRow.uColumns; i++) + pl_advance_cursor(0.0f, (float)(ptClipper->uItemCount - ptClipper->uDisplayEnd) * ptClipper->_fItemHeight); + } + + ptClipper->uDisplayStart = 0; + ptClipper->uDisplayEnd = 0; + ptClipper->_fItemHeight = 0.0f; + ptClipper->_fStartPosY = 0.0f; + ptClipper->uItemCount = 0; + return false; + } +} + +void +pl_layout_dynamic(float fHeight, uint32_t uWidgetCount) +{ + plUiWindow* ptWindow = gptCtx->ptCurrentWindow; + plUiLayoutRow tNewRow = { + .fHeight = fHeight, + .fSpecifiedHeight = fHeight, + .tType = PL_UI_LAYOUT_ROW_TYPE_DYNAMIC, + .tSystemType = PL_UI_LAYOUT_SYSTEM_TYPE_DYNAMIC, + .uColumns = uWidgetCount, + .fWidth = 1.0f / (float)uWidgetCount + }; + ptWindow->tTempData.tCurrentLayoutRow = tNewRow; +} + +void +pl_layout_static(float fHeight, float fWidth, uint32_t uWidgetCount) +{ + plUiWindow* ptWindow = gptCtx->ptCurrentWindow; + plUiLayoutRow tNewRow = { + .fHeight = fHeight, + .fSpecifiedHeight = fHeight, + .tType = PL_UI_LAYOUT_ROW_TYPE_STATIC, + .tSystemType = PL_UI_LAYOUT_SYSTEM_TYPE_STATIC, + .uColumns = uWidgetCount, + .fWidth = fWidth + }; + ptWindow->tTempData.tCurrentLayoutRow = tNewRow; +} + +void +pl_layout_row_begin(plUiLayoutRowType tType, float fHeight, uint32_t uWidgetCount) +{ + plUiWindow* ptWindow = gptCtx->ptCurrentWindow; + plUiLayoutRow tNewRow = { + .fHeight = fHeight, + .fSpecifiedHeight = fHeight, + .tType = tType, + .tSystemType = PL_UI_LAYOUT_SYSTEM_TYPE_ROW_XXX, + .uColumns = uWidgetCount + }; + ptWindow->tTempData.tCurrentLayoutRow = tNewRow; +} + +void +pl_layout_row_push(float fWidth) +{ + plUiWindow* ptWindow = gptCtx->ptCurrentWindow; + plUiLayoutRow* ptCurrentRow = &ptWindow->tTempData.tCurrentLayoutRow; + PL_ASSERT(ptCurrentRow->tSystemType == PL_UI_LAYOUT_SYSTEM_TYPE_ROW_XXX); + ptCurrentRow->fWidth = fWidth; +} + +void +pl_layout_row_end(void) +{ + plUiWindow* ptWindow = gptCtx->ptCurrentWindow; + plUiLayoutRow* ptCurrentRow = &ptWindow->tTempData.tCurrentLayoutRow; + PL_ASSERT(ptCurrentRow->tSystemType == PL_UI_LAYOUT_SYSTEM_TYPE_ROW_XXX); + ptWindow->tTempData.tCursorMaxPos.x = pl_maxf(ptWindow->tTempData.tRowPos.x + ptCurrentRow->fMaxWidth, ptWindow->tTempData.tCursorMaxPos.x); + ptWindow->tTempData.tCursorMaxPos.y = pl_maxf(ptWindow->tTempData.tRowPos.y + ptCurrentRow->fMaxHeight, ptWindow->tTempData.tCursorMaxPos.y); + ptWindow->tTempData.tRowPos.y = ptWindow->tTempData.tRowPos.y + ptCurrentRow->fMaxHeight + gptCtx->tStyle.tItemSpacing.y; + + // temp + plUiLayoutRow tNewRow = {0}; + ptWindow->tTempData.tCurrentLayoutRow = tNewRow; +} + +void +pl_layout_template_begin(float fHeight) +{ + plUiWindow* ptWindow = gptCtx->ptCurrentWindow; + plUiLayoutRow tNewRow = { + .fHeight = fHeight, + .fSpecifiedHeight = fHeight, + .tType = PL_UI_LAYOUT_ROW_TYPE_NONE, + .tSystemType = PL_UI_LAYOUT_SYSTEM_TYPE_TEMPLATE, + .uColumns = 0, + .uEntryStartIndex = pl_sb_size(ptWindow->sbtRowTemplateEntries) + }; + ptWindow->tTempData.tCurrentLayoutRow = tNewRow; +} + +void +pl_layout_template_push_dynamic(void) +{ + plUiWindow* ptWindow = gptCtx->ptCurrentWindow; + plUiLayoutRow* ptCurrentRow = &ptWindow->tTempData.tCurrentLayoutRow; + ptCurrentRow->uDynamicEntryCount++; + pl_sb_add(ptWindow->sbtRowTemplateEntries); + pl_sb_back(ptWindow->sbtRowTemplateEntries).tType = PL_UI_LAYOUT_ROW_ENTRY_TYPE_DYNAMIC; + pl_sb_back(ptWindow->sbtRowTemplateEntries).fWidth = 0.0f; + ptCurrentRow->uColumns++; +} + +void +pl_layout_template_push_variable(float fWidth) +{ + plUiWindow* ptWindow = gptCtx->ptCurrentWindow; + plUiLayoutRow* ptCurrentRow = &ptWindow->tTempData.tCurrentLayoutRow; + ptCurrentRow->uVariableEntryCount++; + ptCurrentRow->fWidth += fWidth; + pl_sb_push(ptWindow->sbuTempLayoutIndexSort, ptCurrentRow->uColumns); + pl_sb_add(ptWindow->sbtRowTemplateEntries); + pl_sb_back(ptWindow->sbtRowTemplateEntries).tType = PL_UI_LAYOUT_ROW_ENTRY_TYPE_VARIABLE; + pl_sb_back(ptWindow->sbtRowTemplateEntries).fWidth = fWidth; + ptCurrentRow->uColumns++; + ptWindow->tTempData.fTempMinWidth += fWidth; +} + +void +pl_layout_template_push_static(float fWidth) +{ + plUiWindow* ptWindow = gptCtx->ptCurrentWindow; + plUiLayoutRow* ptCurrentRow = &ptWindow->tTempData.tCurrentLayoutRow; + ptCurrentRow->fWidth += fWidth; + ptCurrentRow->uStaticEntryCount++; + pl_sb_add(ptWindow->sbtRowTemplateEntries); + pl_sb_back(ptWindow->sbtRowTemplateEntries).tType = PL_UI_LAYOUT_ROW_ENTRY_TYPE_STATIC; + pl_sb_back(ptWindow->sbtRowTemplateEntries).fWidth = fWidth; + ptCurrentRow->uColumns++; + ptWindow->tTempData.fTempStaticWidth += fWidth; + ptWindow->tTempData.fTempMinWidth += fWidth; +} + +void +pl_layout_template_end(void) +{ + plUiWindow* ptWindow = gptCtx->ptCurrentWindow; + plUiLayoutRow* ptCurrentRow = &ptWindow->tTempData.tCurrentLayoutRow; + PL_ASSERT(ptCurrentRow->tSystemType == PL_UI_LAYOUT_SYSTEM_TYPE_TEMPLATE); + ptWindow->tTempData.tCursorMaxPos.x = pl_maxf(ptWindow->tTempData.tRowPos.x + ptCurrentRow->fMaxWidth, ptWindow->tTempData.tCursorMaxPos.x); + ptWindow->tTempData.tCursorMaxPos.y = pl_maxf(ptWindow->tTempData.tRowPos.y + ptCurrentRow->fMaxHeight, ptWindow->tTempData.tCursorMaxPos.y); + ptWindow->tTempData.tRowPos.y = ptWindow->tTempData.tRowPos.y + ptCurrentRow->fMaxHeight + gptCtx->tStyle.tItemSpacing.y; + + // total available width minus padding/spacing + float fWidthAvailable = 0.0f; + if(ptWindow->bScrollbarY) + fWidthAvailable = (ptWindow->tSize.x - gptCtx->tStyle.fWindowHorizontalPadding * 2.0f - gptCtx->tStyle.tItemSpacing.x * (float)(ptCurrentRow->uColumns - 1) - 2.0f - gptCtx->tStyle.fScrollbarSize - (float)gptCtx->ptCurrentWindow->tTempData.uTreeDepth * gptCtx->tStyle.fIndentSize); + else + fWidthAvailable = (ptWindow->tSize.x - gptCtx->tStyle.fWindowHorizontalPadding * 2.0f - gptCtx->tStyle.tItemSpacing.x * (float)(ptCurrentRow->uColumns - 1) - (float)gptCtx->ptCurrentWindow->tTempData.uTreeDepth * gptCtx->tStyle.fIndentSize); + + // simplest cast, not enough room, so nothing left to distribute to dynamic widths + if(ptWindow->tTempData.fTempMinWidth >= fWidthAvailable) + { + for(uint32_t i = 0; i < ptCurrentRow->uColumns; i++) + { + plUiLayoutRowEntry* ptEntry = &ptWindow->sbtRowTemplateEntries[ptCurrentRow->uEntryStartIndex + i]; + if(ptEntry->tType == PL_UI_LAYOUT_ROW_ENTRY_TYPE_DYNAMIC) + ptEntry->fWidth = 0.0f; + } + } + else if((ptCurrentRow->uDynamicEntryCount + ptCurrentRow->uVariableEntryCount) != 0) + { + + // sort large to small (slow bubble sort, should replace later) + bool bSwapOccured = true; + while(bSwapOccured) + { + if(ptCurrentRow->uVariableEntryCount == 0) + break; + bSwapOccured = false; + for(uint32_t i = 0; i < ptCurrentRow->uVariableEntryCount - 1; i++) + { + const uint32_t ii = ptWindow->sbuTempLayoutIndexSort[i]; + const uint32_t jj = ptWindow->sbuTempLayoutIndexSort[i + 1]; + + plUiLayoutRowEntry* ptEntry0 = &ptWindow->sbtRowTemplateEntries[ptCurrentRow->uEntryStartIndex + ii]; + plUiLayoutRowEntry* ptEntry1 = &ptWindow->sbtRowTemplateEntries[ptCurrentRow->uEntryStartIndex + jj]; + + if(ptEntry0->fWidth < ptEntry1->fWidth) + { + ptWindow->sbuTempLayoutIndexSort[i] = jj; + ptWindow->sbuTempLayoutIndexSort[i + 1] = ii; + bSwapOccured = true; + } + } + } + + // add dynamic to the end + if(ptCurrentRow->uDynamicEntryCount > 0) + { + + // dynamic entries appended to the end so they will be "sorted" from the get go + for(uint32_t i = 0; i < ptCurrentRow->uColumns; i++) + { + plUiLayoutRowEntry* ptEntry = &ptWindow->sbtRowTemplateEntries[ptCurrentRow->uEntryStartIndex + i]; + if(ptEntry->tType == PL_UI_LAYOUT_ROW_ENTRY_TYPE_DYNAMIC) + pl_sb_push(ptWindow->sbuTempLayoutIndexSort, i); + } + } + + // organize into levels + float fCurrentWidth = -10000.0f; + for(uint32_t i = 0; i < ptCurrentRow->uVariableEntryCount; i++) + { + const uint32_t ii = ptWindow->sbuTempLayoutIndexSort[i]; + plUiLayoutRowEntry* ptEntry = &ptWindow->sbtRowTemplateEntries[ptCurrentRow->uEntryStartIndex + ii]; + + if(ptEntry->fWidth == fCurrentWidth) + { + pl_sb_back(ptWindow->sbtTempLayoutSort).uCount++; + } + else + { + const plUiLayoutSortLevel tNewSortLevel = { + .fWidth = ptEntry->fWidth, + .uCount = 1, + .uStartIndex = i + }; + pl_sb_push(ptWindow->sbtTempLayoutSort, tNewSortLevel); + fCurrentWidth = ptEntry->fWidth; + } + } + + // add dynamic to the end + if(ptCurrentRow->uDynamicEntryCount > 0) + { + const plUiLayoutSortLevel tInitialSortLevel = { + .fWidth = 0.0f, + .uCount = ptCurrentRow->uDynamicEntryCount, + .uStartIndex = ptCurrentRow->uVariableEntryCount + }; + pl_sb_push(ptWindow->sbtTempLayoutSort, tInitialSortLevel); + } + + // calculate left over width + float fExtraWidth = fWidthAvailable - ptWindow->tTempData.fTempMinWidth; + + // distribute to levels + const uint32_t uLevelCount = pl_sb_size(ptWindow->sbtTempLayoutSort); + if(uLevelCount == 1) + { + plUiLayoutSortLevel tCurrentSortLevel = pl_sb_pop(ptWindow->sbtTempLayoutSort); + const float fDistributableWidth = fExtraWidth / (float)tCurrentSortLevel.uCount; + for(uint32_t i = tCurrentSortLevel.uStartIndex; i < tCurrentSortLevel.uCount; i++) + { + plUiLayoutRowEntry* ptEntry = &ptWindow->sbtRowTemplateEntries[ptCurrentRow->uEntryStartIndex + ptWindow->sbuTempLayoutIndexSort[i]]; + ptEntry->fWidth += fDistributableWidth; + } + } + else + { + while(fExtraWidth > 0.0f) + { + plUiLayoutSortLevel tCurrentSortLevel = pl_sb_pop(ptWindow->sbtTempLayoutSort); + + if(pl_sb_size(ptWindow->sbtTempLayoutSort) == 0) // final + { + const float fDistributableWidth = fExtraWidth / (float)tCurrentSortLevel.uCount; + for(uint32_t i = tCurrentSortLevel.uStartIndex; i < tCurrentSortLevel.uStartIndex + tCurrentSortLevel.uCount; i++) + { + plUiLayoutRowEntry* ptEntry = &ptWindow->sbtRowTemplateEntries[ptCurrentRow->uEntryStartIndex + ptWindow->sbuTempLayoutIndexSort[i]]; + ptEntry->fWidth += fDistributableWidth; + } + break; + } + + const float fDelta = pl_sb_back(ptWindow->sbtTempLayoutSort).fWidth - tCurrentSortLevel.fWidth; + const float fTotalOwed = fDelta * (float)tCurrentSortLevel.uCount; + + if(fTotalOwed < fExtraWidth) // perform operations + { + for(uint32_t i = tCurrentSortLevel.uStartIndex; i < tCurrentSortLevel.uStartIndex + tCurrentSortLevel.uCount; i++) + { + plUiLayoutRowEntry* ptEntry = &ptWindow->sbtRowTemplateEntries[ptCurrentRow->uEntryStartIndex + ptWindow->sbuTempLayoutIndexSort[i]]; + ptEntry->fWidth += fDelta; + } + pl_sb_back(ptWindow->sbtTempLayoutSort).uCount += tCurrentSortLevel.uCount; + fExtraWidth -= fTotalOwed; + } + else // do the best we can + { + const float fDistributableWidth = fExtraWidth / (float)tCurrentSortLevel.uCount; + for(uint32_t i = tCurrentSortLevel.uStartIndex; i < tCurrentSortLevel.uStartIndex + tCurrentSortLevel.uCount; i++) + { + plUiLayoutRowEntry* ptEntry = &ptWindow->sbtRowTemplateEntries[ptCurrentRow->uEntryStartIndex + ptWindow->sbuTempLayoutIndexSort[i]]; + ptEntry->fWidth += fDistributableWidth; + } + fExtraWidth = 0.0f; + } + } + } + + } + + pl_sb_reset(ptWindow->sbuTempLayoutIndexSort); + pl_sb_reset(ptWindow->sbtTempLayoutSort); + ptWindow->tTempData.fTempMinWidth = 0.0f; + ptWindow->tTempData.fTempStaticWidth = 0.0f; +} + +void +pl_layout_space_begin(plUiLayoutRowType tType, float fHeight, uint32_t uWidgetCount) +{ + plUiWindow* ptWindow = gptCtx->ptCurrentWindow; + plUiLayoutRow tNewRow = { + .fHeight = fHeight, + .fSpecifiedHeight = tType == PL_UI_LAYOUT_ROW_TYPE_DYNAMIC ? fHeight : 1.0f, + .tType = tType, + .tSystemType = PL_UI_LAYOUT_SYSTEM_TYPE_SPACE, + .uColumns = uWidgetCount + }; + ptWindow->tTempData.tCurrentLayoutRow = tNewRow; +} + +void +pl_layout_space_push(float fX, float fY, float fWidth, float fHeight) +{ + plUiWindow* ptWindow = gptCtx->ptCurrentWindow; + plUiLayoutRow* ptCurrentRow = &ptWindow->tTempData.tCurrentLayoutRow; + + PL_ASSERT(ptCurrentRow->tSystemType == PL_UI_LAYOUT_SYSTEM_TYPE_SPACE); + ptCurrentRow->fHorizontalOffset = ptCurrentRow->tType == PL_UI_LAYOUT_ROW_TYPE_DYNAMIC ? fX * ptWindow->tSize.x : fX; + ptCurrentRow->fVerticalOffset = fY * ptCurrentRow->fSpecifiedHeight; + ptCurrentRow->fWidth = fWidth; + ptCurrentRow->fHeight = fHeight * ptCurrentRow->fSpecifiedHeight; +} + +void +pl_layout_space_end(void) +{ + plUiWindow* ptWindow = gptCtx->ptCurrentWindow; + plUiLayoutRow* ptCurrentRow = &ptWindow->tTempData.tCurrentLayoutRow; + PL_ASSERT(ptCurrentRow->tSystemType == PL_UI_LAYOUT_SYSTEM_TYPE_SPACE); + ptWindow->tTempData.tCursorMaxPos.x = pl_maxf(ptWindow->tTempData.tRowPos.x + ptCurrentRow->fMaxWidth, ptWindow->tTempData.tCursorMaxPos.x); + ptWindow->tTempData.tCursorMaxPos.y = pl_maxf(ptWindow->tTempData.tRowPos.y + ptCurrentRow->fMaxHeight, ptWindow->tTempData.tCursorMaxPos.y); + ptWindow->tTempData.tRowPos.y = ptWindow->tTempData.tRowPos.y + ptCurrentRow->fMaxHeight + gptCtx->tStyle.tItemSpacing.y; + + // temp + plUiLayoutRow tNewRow = {0}; + ptWindow->tTempData.tCurrentLayoutRow = tNewRow; +} + +bool +pl_was_last_item_hovered(void) +{ + return gptCtx->tPrevItemData.bHovered; +} + +bool +pl_was_last_item_active(void) +{ + return gptCtx->tPrevItemData.bActive; +} + +int +pl_get_int(plUiStorage* ptStorage, uint32_t uKey, int iDefaultValue) +{ + plUiStorageEntry* ptIterator = pl_lower_bound(ptStorage->sbtData, uKey); + if((ptIterator == pl_sb_end(ptStorage->sbtData)) || (ptIterator->uKey != uKey)) + return iDefaultValue; + return ptIterator->iValue; +} + +float +pl_get_float(plUiStorage* ptStorage, uint32_t uKey, float fDefaultValue) +{ + plUiStorageEntry* ptIterator = pl_lower_bound(ptStorage->sbtData, uKey); + if((ptIterator == pl_sb_end(ptStorage->sbtData)) || (ptIterator->uKey != uKey)) + return fDefaultValue; + return ptIterator->fValue; +} + +bool +pl_get_bool(plUiStorage* ptStorage, uint32_t uKey, bool bDefaultValue) +{ + return pl_get_int(ptStorage, uKey, bDefaultValue ? 1 : 0) != 0; +} + +void* +pl_get_ptr(plUiStorage* ptStorage, uint32_t uKey) +{ + plUiStorageEntry* ptIterator = pl_lower_bound(ptStorage->sbtData, uKey); + if((ptIterator == pl_sb_end(ptStorage->sbtData)) || (ptIterator->uKey != uKey)) + return NULL; + return ptIterator->pValue; +} + +int* +pl_get_int_ptr(plUiStorage* ptStorage, uint32_t uKey, int iDefaultValue) +{ + plUiStorageEntry* ptIterator = pl_lower_bound(ptStorage->sbtData, uKey); + if(ptIterator == pl_sb_end(ptStorage->sbtData) || (ptIterator->uKey != uKey)) + { + uint32_t uIndex = (uint32_t)((uintptr_t)ptIterator - (uintptr_t)ptStorage->sbtData) / (uint32_t)sizeof(plUiStorageEntry); + pl_sb_insert(ptStorage->sbtData, uIndex, ((plUiStorageEntry){.uKey = uKey, .iValue = iDefaultValue})); + ptIterator = &ptStorage->sbtData[uIndex]; + } + return &ptIterator->iValue; +} + +float* +pl_get_float_ptr(plUiStorage* ptStorage, uint32_t uKey, float fDefaultValue) +{ + plUiStorageEntry* ptIterator = pl_lower_bound(ptStorage->sbtData, uKey); + if(ptIterator == pl_sb_end(ptStorage->sbtData) || (ptIterator->uKey != uKey)) + { + uint32_t uIndex = (uint32_t)((uintptr_t)ptIterator - (uintptr_t)ptStorage->sbtData) / (uint32_t)sizeof(plUiStorageEntry); + pl_sb_insert(ptStorage->sbtData, uIndex, ((plUiStorageEntry){.uKey = uKey, .fValue = fDefaultValue})); + ptIterator = &ptStorage->sbtData[uIndex]; + } + return &ptIterator->fValue; +} + +bool* +pl_get_bool_ptr(plUiStorage* ptStorage, uint32_t uKey, bool bDefaultValue) +{ + return (bool*)pl_get_int_ptr(ptStorage, uKey, bDefaultValue ? 1 : 0); +} + +void** +pl_get_ptr_ptr(plUiStorage* ptStorage, uint32_t uKey, void* pDefaultValue) +{ + plUiStorageEntry* ptIterator = pl_lower_bound(ptStorage->sbtData, uKey); + if(ptIterator == pl_sb_end(ptStorage->sbtData) || (ptIterator->uKey != uKey)) + { + uint32_t uIndex = (uint32_t)((uintptr_t)ptIterator - (uintptr_t)ptStorage->sbtData) / (uint32_t)sizeof(plUiStorageEntry); + pl_sb_insert(ptStorage->sbtData, uIndex, ((plUiStorageEntry){.uKey = uKey, .pValue = pDefaultValue})); + ptIterator = &ptStorage->sbtData[uIndex]; + } + return &ptIterator->pValue; +} + +void +pl_set_int(plUiStorage* ptStorage, uint32_t uKey, int iValue) +{ + plUiStorageEntry* ptIterator = pl_lower_bound(ptStorage->sbtData, uKey); + if(ptIterator == pl_sb_end(ptStorage->sbtData) || (ptIterator->uKey != uKey)) + { + uint32_t uIndex = (uint32_t)((uintptr_t)ptIterator - (uintptr_t)ptStorage->sbtData) / (uint32_t)sizeof(plUiStorageEntry); + pl_sb_insert(ptStorage->sbtData, uIndex, ((plUiStorageEntry){.uKey = uKey, .iValue = iValue})); + return; + } + ptIterator->iValue = iValue; +} + +void +pl_set_float(plUiStorage* ptStorage, uint32_t uKey, float fValue) +{ + plUiStorageEntry* ptIterator = pl_lower_bound(ptStorage->sbtData, uKey); + if(ptIterator == pl_sb_end(ptStorage->sbtData) || (ptIterator->uKey != uKey)) + { + uint32_t uIndex = (uint32_t)((uintptr_t)ptIterator - (uintptr_t)ptStorage->sbtData) / (uint32_t)sizeof(plUiStorageEntry); + pl_sb_insert(ptStorage->sbtData, uIndex, ((plUiStorageEntry){.uKey = uKey, .fValue = fValue})); + return; + } + ptIterator->fValue = fValue; +} + +void +pl_set_bool(plUiStorage* ptStorage, uint32_t uKey, bool bValue) +{ + pl_set_int(ptStorage, uKey, bValue ? 1 : 0); +} + +void +pl_set_ptr(plUiStorage* ptStorage, uint32_t uKey, void* pValue) +{ + plUiStorageEntry* ptIterator = pl_lower_bound(ptStorage->sbtData, uKey); + if(ptIterator == pl_sb_end(ptStorage->sbtData) || (ptIterator->uKey != uKey)) + { + uint32_t uIndex = (uint32_t)((uintptr_t)ptIterator - (uintptr_t)ptStorage->sbtData) / (uint32_t)sizeof(plUiStorageEntry); + pl_sb_insert(ptStorage->sbtData, uIndex, ((plUiStorageEntry){.uKey = uKey, .pValue = pValue})); + return; + } + ptIterator->pValue = pValue; +} + +const char* +pl_find_renderered_text_end(const char* pcText, const char* pcTextEnd) +{ + const char* pcTextDisplayEnd = pcText; + if (!pcTextEnd) + pcTextEnd = (const char*)-1; + + while (pcTextDisplayEnd < pcTextEnd && *pcTextDisplayEnd != '\0' && (pcTextDisplayEnd[0] != '#' || pcTextDisplayEnd[1] != '#')) + pcTextDisplayEnd++; + return pcTextDisplayEnd; +} + +bool +pl_is_item_hoverable(const plRect* ptBox, uint32_t uHash) +{ + plUiWindow* ptWindow = gptCtx->ptCurrentWindow; + + if(gptCtx->ptHoveredWindow != gptCtx->ptCurrentWindow) + return false; + + if(!gptIOI->is_mouse_hovering_rect(ptBox->tMin, ptBox->tMax)) + return false; + + // check if another item is already hovered + if(gptCtx->uHoveredId != 0 && gptCtx->uHoveredId != uHash) + return false; + + // check if another item is already active + if(gptCtx->uActiveId != 0 && gptCtx->uActiveId != uHash) + return false; + + return true; +} + +bool +pl_is_item_hoverable_circle(plVec2 tP, float fRadius, uint32_t uHash) +{ + plUiWindow* ptWindow = gptCtx->ptCurrentWindow; + + if(gptCtx->ptHoveredWindow != gptCtx->ptCurrentWindow) + return false; + + // hovered & active ID must be some value but not ours + if(gptCtx->uHoveredId != 0 && gptCtx->uHoveredId != uHash) + return false; + + // active ID must be not used or ours + if(!(gptCtx->uActiveId == uHash || gptCtx->uActiveId == 0)) + return false; + + return pl_does_circle_contain_point(tP, fRadius, gptIOI->get_mouse_pos()); +} + +void +pl_ui_add_text(plDrawLayer2D* ptLayer, plFont* ptFont, float fSize, plVec2 tP, plVec4 tColor, const char* pcText, float fWrap) +{ + const char* pcTextEnd = pcText + strlen(pcText); + gptDraw->add_text_ex(ptLayer, ptFont, fSize, (plVec2){roundf(tP.x), roundf(tP.y)}, tColor, pcText, pl_find_renderered_text_end(pcText, pcTextEnd), fWrap); +} + +void +pl_add_clipped_text(plDrawLayer2D* ptLayer, plFont* ptFont, float fSize, plVec2 tP, plVec2 tMin, plVec2 tMax, plVec4 tColor, const char* pcText, float fWrap) +{ + const char* pcTextEnd = pcText + strlen(pcText); + gptDraw->add_text_clipped_ex(ptLayer, ptFont, fSize, (plVec2){roundf(tP.x + 0.5f), roundf(tP.y + 0.5f)}, tMin, tMax, tColor, pcText, pl_find_renderered_text_end(pcText, pcTextEnd), fWrap); +} + +plVec2 +pl_ui_calculate_text_size(plFont* font, float size, const char* text, float wrap) +{ + const char* pcTextEnd = text + strlen(text); + return gptDraw->calculate_text_size_ex(font, size, text, pl_find_renderered_text_end(text, pcTextEnd), wrap); +} + +bool +pl_does_triangle_contain_point(plVec2 p0, plVec2 p1, plVec2 p2, plVec2 point) +{ + bool b1 = ((point.x - p1.x) * (p0.y - p1.y) - (point.y - p1.y) * (p0.x - p1.x)) < 0.0f; + bool b2 = ((point.x - p2.x) * (p1.y - p2.y) - (point.y - p2.y) * (p1.x - p2.x)) < 0.0f; + bool b3 = ((point.x - p0.x) * (p2.y - p0.y) - (point.y - p0.y) * (p2.x - p0.x)) < 0.0f; + return ((b1 == b2) && (b2 == b3)); +} + +plUiStorageEntry* +pl_lower_bound(plUiStorageEntry* sbtData, uint32_t uKey) +{ + plUiStorageEntry* ptFirstEntry = sbtData; + uint32_t uCount = pl_sb_size(sbtData); + while (uCount > 0) + { + uint32_t uCount2 = uCount >> 1; + plUiStorageEntry* ptMiddleEntry = ptFirstEntry + uCount2; + if(ptMiddleEntry->uKey < uKey) + { + ptFirstEntry = ++ptMiddleEntry; + uCount -= uCount2 + 1; + } + else + uCount = uCount2; + } + + return ptFirstEntry; +} + +bool +pl_begin_window_ex(const char* pcName, bool* pbOpen, plUiWindowFlags tFlags) +{ + plUiWindow* ptWindow = NULL; // window we are working on + plUiWindow* ptParentWindow = gptCtx->ptCurrentWindow; // parent window if there any + + // generate hashed ID + const uint32_t uWindowID = pl_str_hash(pcName, 0, ptParentWindow ? pl_sb_top(gptCtx->sbuIdStack) : 0); + pl_sb_push(gptCtx->sbuIdStack, uWindowID); + + // title text & title bar sizes + const plVec2 tTextSize = pl_ui_calculate_text_size(gptCtx->ptFont, gptCtx->tStyle.fFontSize, pcName, 0.0f); + float fTitleBarHeight = (tFlags & PL_UI_WINDOW_FLAGS_CHILD_WINDOW) ? 0.0f : gptCtx->tStyle.fFontSize + 2.0f * gptCtx->tStyle.fTitlePadding; + + // see if window already exist in storage + ptWindow = pl_get_ptr(&gptCtx->tWindows, uWindowID); + + // new window needs to be created + if(ptWindow == NULL) + { + // allocate new window + ptWindow = PL_ALLOC(sizeof(plUiWindow)); + memset(ptWindow, 0, sizeof(plUiWindow)); + ptWindow->uId = uWindowID; + ptWindow->pcName = pcName; + ptWindow->tPos = (plVec2){ 200.0f, 200.0f}; + ptWindow->tMinSize = (plVec2){ 200.0f, 200.0f}; + ptWindow->tMaxSize = (plVec2){ 10000.0f, 10000.0f}; + ptWindow->tSize = (plVec2){ 500.0f, 500.0f}; + ptWindow->ptBgLayer = gptDraw->request_2d_layer(gptCtx->ptDrawlist, pcName); + ptWindow->ptFgLayer = gptDraw->request_2d_layer(gptCtx->ptDrawlist, pcName); + ptWindow->tPosAllowableFlags = PL_UI_COND_ALWAYS | PL_UI_COND_ONCE; + ptWindow->tSizeAllowableFlags = PL_UI_COND_ALWAYS | PL_UI_COND_ONCE; + ptWindow->tCollapseAllowableFlags = PL_UI_COND_ALWAYS | PL_UI_COND_ONCE; + ptWindow->ptParentWindow = (tFlags & PL_UI_WINDOW_FLAGS_CHILD_WINDOW) ? ptParentWindow : ptWindow; + ptWindow->uFocusOrder = pl_sb_size(gptCtx->sbptFocusedWindows); + ptWindow->tFlags = PL_UI_WINDOW_FLAGS_NONE; + + // add to focused windows if not a child + if(!(tFlags & PL_UI_WINDOW_FLAGS_CHILD_WINDOW)) + { + pl_sb_push(gptCtx->sbptFocusedWindows, ptWindow); + ptWindow->ptRootWindow = ptWindow; + } + else + ptWindow->ptRootWindow = ptParentWindow->ptRootWindow; + + // add window to storage + pl_set_ptr(&gptCtx->tWindows, uWindowID, ptWindow); + } + + // seen this frame (obviously) + + ptWindow->bActive = true; + ptWindow->tFlags = tFlags; + + if(tFlags & PL_UI_WINDOW_FLAGS_CHILD_WINDOW) + { + + plUiLayoutRow* ptCurrentRow = &ptParentWindow->tTempData.tCurrentLayoutRow; + const plVec2 tStartPos = pl__ui_get_cursor_pos(); + + // set window position to parent window current cursor + ptWindow->tPos = tStartPos; + + pl_sb_push(ptParentWindow->sbtChildWindows, ptWindow); + } + gptCtx->ptCurrentWindow = ptWindow; + + // reset per frame window temporary data + memset(&ptWindow->tTempData, 0, sizeof(plUiTempWindowData)); + pl_sb_reset(ptWindow->sbtChildWindows); + pl_sb_reset(ptWindow->sbtRowStack); + pl_sb_reset(ptWindow->sbtRowTemplateEntries); + + // clamp window size to min/max + ptWindow->tSize = pl_clamp_vec2(ptWindow->tMinSize, ptWindow->tSize, ptWindow->tMaxSize); + + // should window collapse + if(gptCtx->tNextWindowData.tFlags & PL_NEXT_WINDOW_DATA_FLAGS_HAS_COLLAPSED) + { + if(ptWindow->tCollapseAllowableFlags & gptCtx->tNextWindowData.tCollapseCondition) + { + ptWindow->bCollapsed = true; + ptWindow->tCollapseAllowableFlags &= ~PL_UI_COND_ONCE; + } + } + + // position & size + plVec2 tStartPos = ptWindow->tPos; + + // next window calls + bool bWindowSizeSet = false; + bool bWindowPosSet = false; + if(gptCtx->tNextWindowData.tFlags & PL_NEXT_WINDOW_DATA_FLAGS_HAS_POS) + { + bWindowPosSet = ptWindow->tPosAllowableFlags & gptCtx->tNextWindowData.tPosCondition; + if(bWindowPosSet) + { + tStartPos = gptCtx->tNextWindowData.tPos; + ptWindow->tPos = tStartPos; + ptWindow->tPosAllowableFlags &= ~PL_UI_COND_ONCE; + } + } + + if(gptCtx->tNextWindowData.tFlags & PL_NEXT_WINDOW_DATA_FLAGS_HAS_SIZE) + { + bWindowSizeSet = ptWindow->tSizeAllowableFlags & gptCtx->tNextWindowData.tSizeCondition; + if(bWindowSizeSet) + { + ptWindow->tSize = gptCtx->tNextWindowData.tSize; + if(ptWindow->tSize.x < 0.0f && ptWindow->tFlags & PL_UI_WINDOW_FLAGS_CHILD_WINDOW) + ptWindow->tSize.x = -(ptWindow->tPos.x - ptParentWindow->tPos.x) + ptParentWindow->tSize.x - ptWindow->tSize.x - (ptParentWindow->bScrollbarY ? gptCtx->tStyle.fScrollbarSize + 2.0f : 0.0f) - (ptWindow->bScrollbarY ? gptCtx->tStyle.fScrollbarSize + 2.0f : 0.0f); + if(ptWindow->tSize.y < 0.0f && ptWindow->tFlags & PL_UI_WINDOW_FLAGS_CHILD_WINDOW) + ptWindow->tSize.y = -(ptWindow->tPos.y - ptParentWindow->tPos.y) + ptParentWindow->tSize.y - ptWindow->tSize.y - (ptParentWindow->bScrollbarX ? gptCtx->tStyle.fScrollbarSize + 2.0f : 0.0f) - (ptWindow->bScrollbarX ? gptCtx->tStyle.fScrollbarSize + 2.0f : 0.0f); + + ptWindow->tSizeAllowableFlags &= ~PL_UI_COND_ONCE; + } + } + + if(ptWindow->bCollapsed) + ptWindow->tSize = (plVec2){ptWindow->tSize.x, fTitleBarHeight}; + + // updating outer rect here but autosized windows do so again in pl_end_window(..) + ptWindow->tOuterRect = pl_calculate_rect(ptWindow->tPos, ptWindow->tSize); + ptWindow->tOuterRectClipped = ptWindow->tOuterRect; + ptWindow->tInnerRect = ptWindow->tOuterRect; + + // remove scrollbars from inner rect + if(ptWindow->bScrollbarX) + ptWindow->tInnerRect.tMax.y -= gptCtx->tStyle.fScrollbarSize + 2.0f; + if(ptWindow->bScrollbarY) + ptWindow->tInnerRect.tMax.x -= gptCtx->tStyle.fScrollbarSize + 2.0f; + + // decorations + if(!(tFlags & PL_UI_WINDOW_FLAGS_NO_TITLE_BAR)) // has title bar + { + + ptWindow->tInnerRect.tMin.y += fTitleBarHeight; + + // draw title bar + plVec4 tTitleColor; + if(ptWindow->uId == gptCtx->uActiveWindowId) + tTitleColor = gptCtx->tColorScheme.tTitleActiveCol; + else if(ptWindow->bCollapsed) + tTitleColor = gptCtx->tColorScheme.tTitleBgCollapsedCol; + else + tTitleColor = gptCtx->tColorScheme.tTitleBgCol; + gptDraw->add_rect_filled(ptWindow->ptFgLayer, tStartPos, pl_add_vec2(tStartPos, (plVec2){ptWindow->tSize.x, fTitleBarHeight}), tTitleColor); + + // draw title text + const plVec2 titlePos = pl_add_vec2(tStartPos, (plVec2){ptWindow->tSize.x / 2.0f - tTextSize.x / 2.0f, gptCtx->tStyle.fTitlePadding}); + pl_ui_add_text(ptWindow->ptFgLayer, gptCtx->ptFont, gptCtx->tStyle.fFontSize, titlePos, gptCtx->tColorScheme.tTextCol, pcName, 0.0f); + + // draw close button + const float fTitleBarButtonRadius = 8.0f; + float fTitleButtonStartPos = fTitleBarButtonRadius * 2.0f; + if(pbOpen) + { + const uint32_t uCloseHash = pl_str_hash("PL_CLOSE", 0, pl_sb_top(gptCtx->sbuIdStack)); + plVec2 tCloseCenterPos = pl_add_vec2(tStartPos, (plVec2){ptWindow->tSize.x - fTitleButtonStartPos, fTitleBarHeight / 2.0f}); + plVec2 tCloseTLPos = {tCloseCenterPos.x - fTitleBarButtonRadius, tCloseCenterPos.y - fTitleBarButtonRadius}; + fTitleButtonStartPos += fTitleBarButtonRadius * 2.0f + gptCtx->tStyle.tItemSpacing.x; + + plRect tBoundingBox = pl_calculate_rect(tCloseTLPos, (plVec2){fTitleBarButtonRadius * 2.0f, fTitleBarButtonRadius * 2.0f}); + bool bHovered = false; + bool bHeld = false; + bool bPressed = pl_button_behavior(&tBoundingBox, uCloseHash, &bHovered, &bHeld); + + if(gptCtx->uActiveId == uCloseHash) gptDraw->add_circle_filled(ptWindow->ptFgLayer, tCloseCenterPos, fTitleBarButtonRadius, (plVec4){1.0f, 0.0f, 0.0f, 1.0f}, 12); + else if(gptCtx->uHoveredId == uCloseHash) gptDraw->add_circle_filled(ptWindow->ptFgLayer, tCloseCenterPos, fTitleBarButtonRadius, (plVec4){1.0f, 0.0f, 0.0f, 1.0f}, 12); + else gptDraw->add_circle_filled(ptWindow->ptFgLayer, tCloseCenterPos, fTitleBarButtonRadius, (plVec4){0.5f, 0.0f, 0.0f, 1.0f}, 12); + + if(bPressed) + *pbOpen = false; + } + + // draw collapse button + if(!(tFlags & PL_UI_WINDOW_FLAGS_NO_COLLAPSE)) + { + const uint32_t uCollapseHash = pl_str_hash("PL_COLLAPSE", 0, pl_sb_top(gptCtx->sbuIdStack)); + plVec2 tCloseCenterPos = pl_add_vec2(tStartPos, (plVec2){ptWindow->tSize.x - fTitleButtonStartPos, fTitleBarHeight / 2.0f}); + plVec2 tCloseTLPos = {tCloseCenterPos.x - fTitleBarButtonRadius, tCloseCenterPos.y - fTitleBarButtonRadius}; + + plRect tBoundingBox = pl_calculate_rect(tCloseTLPos, (plVec2){fTitleBarButtonRadius * 2.0f, fTitleBarButtonRadius * 2.0f}); + bool bHovered = false; + bool bHeld = false; + bool bPressed = pl_button_behavior(&tBoundingBox, uCollapseHash, &bHovered, &bHeld); + + if(gptCtx->uActiveId == uCollapseHash) gptDraw->add_circle_filled(ptWindow->ptFgLayer, tCloseCenterPos, fTitleBarButtonRadius, (plVec4){1.0f, 1.0f, 0.0f, 1.0f}, 12); + else if(gptCtx->uHoveredId == uCollapseHash) gptDraw->add_circle_filled(ptWindow->ptFgLayer, tCloseCenterPos, fTitleBarButtonRadius, (plVec4){1.0f, 1.0f, 0.0f, 1.0f}, 12); + else gptDraw->add_circle_filled(ptWindow->ptFgLayer, tCloseCenterPos, fTitleBarButtonRadius, (plVec4){0.5f, 0.5f, 0.0f, 1.0f}, 12); + + if(bPressed) + { + ptWindow->bCollapsed = !ptWindow->bCollapsed; + if(!ptWindow->bCollapsed) + { + ptWindow->tSize = ptWindow->tFullSize; + if(tFlags & PL_UI_WINDOW_FLAGS_AUTO_SIZE) + ptWindow->uHideFrames = 2; + } + } + } + + } + else + fTitleBarHeight = 0.0f; + + // remove padding for inner clip rect + ptWindow->tInnerClipRect = pl_rect_expand_vec2(&ptWindow->tInnerRect, (plVec2){-gptCtx->tStyle.fWindowHorizontalPadding, 0.0f}); + + if(!ptWindow->bCollapsed) + { + const plVec2 tStartClip = { ptWindow->tPos.x, ptWindow->tPos.y + fTitleBarHeight }; + + const plVec2 tInnerClip = { + ptWindow->tSize.x - (ptWindow->bScrollbarY ? gptCtx->tStyle.fScrollbarSize + 2.0f : 0.0f), + ptWindow->tSize.y - fTitleBarHeight - (ptWindow->bScrollbarX ? gptCtx->tStyle.fScrollbarSize + 2.0f : 0.0f) + }; + + const plRect* ptClipRect = gptDraw->get_clip_rect(gptCtx->ptDrawlist); + + if(ptClipRect) + { + ptWindow->tInnerClipRect = pl_rect_clip_full(&ptWindow->tInnerClipRect, ptClipRect); + ptWindow->tOuterRectClipped = pl_rect_clip_full(&ptWindow->tOuterRectClipped, ptClipRect); + } + gptDraw->push_clip_rect(gptCtx->ptDrawlist, ptWindow->tInnerClipRect, false); + } + + // update cursors + ptWindow->tTempData.tCursorStartPos.x = gptCtx->tStyle.fWindowHorizontalPadding + tStartPos.x - ptWindow->tScroll.x; + ptWindow->tTempData.tCursorStartPos.y = gptCtx->tStyle.fWindowVerticalPadding + tStartPos.y + fTitleBarHeight - ptWindow->tScroll.y; + ptWindow->tTempData.tRowPos = ptWindow->tTempData.tCursorStartPos; + ptWindow->tTempData.tCursorStartPos = pl_floor_vec2(ptWindow->tTempData.tCursorStartPos); + ptWindow->tTempData.fTitleBarHeight = fTitleBarHeight; + + // reset next window flags + gptCtx->tNextWindowData.tFlags = PL_NEXT_WINDOW_DATA_FLAGS_NONE; + + + if(tFlags & PL_UI_WINDOW_FLAGS_CHILD_WINDOW) + { + ptWindow->bVisible = pl_rect_overlaps_rect(&ptWindow->tInnerClipRect, &ptParentWindow->tInnerClipRect); + return ptWindow->bVisible && !pl_rect_is_inverted(&ptWindow->tInnerClipRect); + } + + ptWindow->bVisible = true; + return !ptWindow->bCollapsed; +} + +void +pl_render_scrollbar(plUiWindow* ptWindow, uint32_t uHash, plUiAxis tAxis) +{ + const plRect tParentBgRect = ptWindow->ptParentWindow->tOuterRect; + if(tAxis == PL_UI_AXIS_X) + { + const float fRightSidePadding = ptWindow->bScrollbarY ? gptCtx->tStyle.fScrollbarSize + 2.0f : 0.0f; + + // this is needed if autosizing and parent changes sizes + ptWindow->tScroll.x = pl_clampf(0.0f, ptWindow->tScroll.x, ptWindow->tScrollMax.x); + + const float fScrollbarHandleSize = pl_maxf(5.0f, floorf((ptWindow->tSize.x - fRightSidePadding) * ((ptWindow->tSize.x - fRightSidePadding) / (ptWindow->tContentSize.x)))); + const float fScrollbarHandleStart = floorf((ptWindow->tSize.x - fRightSidePadding - fScrollbarHandleSize) * (ptWindow->tScroll.x/(ptWindow->tScrollMax.x))); + + const plVec2 tStartPos = pl_add_vec2(ptWindow->tPos, (plVec2){fScrollbarHandleStart, ptWindow->tSize.y - gptCtx->tStyle.fScrollbarSize - 2.0f}); + + plRect tScrollBackground = { + pl_add_vec2(ptWindow->tPos, (plVec2){0.0f, ptWindow->tSize.y - gptCtx->tStyle.fScrollbarSize - 2.0f}), + pl_add_vec2(ptWindow->tPos, (plVec2){ptWindow->tSize.x - fRightSidePadding, ptWindow->tSize.y - 2.0f}) + }; + + if(pl_rect_overlaps_rect(&tParentBgRect, &tScrollBackground)) + { + const plVec2 tFinalSize = {fScrollbarHandleSize, gptCtx->tStyle.fScrollbarSize}; + plRect tHandleBox = pl_calculate_rect(tStartPos, tFinalSize); + tScrollBackground = pl_rect_clip(&tScrollBackground, &ptWindow->tOuterRectClipped); + tHandleBox = pl_rect_clip(&tHandleBox, &ptWindow->tOuterRectClipped); + + gptDraw->add_rect_filled(ptWindow->ptBgLayer, tScrollBackground.tMin, tScrollBackground.tMax, gptCtx->tColorScheme.tScrollbarBgCol); + + bool bHovered = false; + bool bHeld = false; + const bool bPressed = pl_button_behavior(&tHandleBox, uHash, &bHovered, &bHeld); + if(gptCtx->uActiveId == uHash) + gptDraw->add_rect_filled(ptWindow->ptBgLayer, tStartPos, pl_add_vec2(tStartPos, tFinalSize), gptCtx->tColorScheme.tScrollbarActiveCol); + else if(gptCtx->uHoveredId == uHash) + gptDraw->add_rect_filled(ptWindow->ptBgLayer, tStartPos, pl_add_vec2(tStartPos, tFinalSize), gptCtx->tColorScheme.tScrollbarHoveredCol); + else + gptDraw->add_rect_filled(ptWindow->ptBgLayer, tStartPos, pl_add_vec2(tStartPos, tFinalSize), gptCtx->tColorScheme.tScrollbarHandleCol); + } + } + else if(tAxis == PL_UI_AXIS_Y) + { + + const float fBottomPadding = ptWindow->bScrollbarX ? gptCtx->tStyle.fScrollbarSize + 2.0f : 0.0f; + const float fTopPadding = (ptWindow->tFlags & PL_UI_WINDOW_FLAGS_CHILD_WINDOW) ? 0.0f : gptCtx->tStyle.fFontSize + 2.0f * gptCtx->tStyle.fTitlePadding; + + // this is needed if autosizing and parent changes sizes + ptWindow->tScroll.y = pl_clampf(0.0f, ptWindow->tScroll.y, ptWindow->tScrollMax.y); + + const float fScrollbarHandleSize = pl_maxf(5.0f, floorf((ptWindow->tSize.y - fTopPadding - fBottomPadding) * ((ptWindow->tSize.y - fTopPadding - fBottomPadding) / (ptWindow->tContentSize.y)))); + const float fScrollbarHandleStart = floorf((ptWindow->tSize.y - fTopPadding - fBottomPadding - fScrollbarHandleSize) * (ptWindow->tScroll.y / (ptWindow->tScrollMax.y))); + + const plVec2 tStartPos = pl_add_vec2(ptWindow->tPos, (plVec2){ptWindow->tSize.x - gptCtx->tStyle.fScrollbarSize - 2.0f, fTopPadding + fScrollbarHandleStart}); + + plRect tScrollBackground = pl_calculate_rect(pl_add_vec2(ptWindow->tPos, + (plVec2){ptWindow->tSize.x - gptCtx->tStyle.fScrollbarSize - 2.0f, fTopPadding}), + (plVec2){gptCtx->tStyle.fScrollbarSize, ptWindow->tSize.y - fBottomPadding}); + + if(pl_rect_overlaps_rect(&tParentBgRect, &tScrollBackground)) + { + + const plVec2 tFinalSize = {gptCtx->tStyle.fScrollbarSize, fScrollbarHandleSize}; + plRect tHandleBox = pl_calculate_rect(tStartPos, tFinalSize); + tScrollBackground = pl_rect_clip(&tScrollBackground, &ptWindow->tOuterRectClipped); + tHandleBox = pl_rect_clip(&tHandleBox, &ptWindow->tOuterRectClipped); + + // scrollbar background + gptDraw->add_rect_filled(ptWindow->ptBgLayer, tScrollBackground.tMin, tScrollBackground.tMax, gptCtx->tColorScheme.tScrollbarBgCol); + + bool bHovered = false; + bool bHeld = false; + const bool bPressed = pl_button_behavior(&tHandleBox, uHash, &bHovered, &bHeld); + + // scrollbar handle + if(gptCtx->uActiveId == uHash) + gptDraw->add_rect_filled(ptWindow->ptBgLayer, tHandleBox.tMin, tHandleBox.tMax, gptCtx->tColorScheme.tScrollbarActiveCol); + else if(gptCtx->uHoveredId == uHash) + gptDraw->add_rect_filled(ptWindow->ptBgLayer, tHandleBox.tMin, tHandleBox.tMax, gptCtx->tColorScheme.tScrollbarHoveredCol); + else + gptDraw->add_rect_filled(ptWindow->ptBgLayer, tHandleBox.tMin, tHandleBox.tMax, gptCtx->tColorScheme.tScrollbarHandleCol); + + } + } + + if(gptCtx->uActiveId == uHash && gptIOI->is_mouse_down(PL_MOUSE_BUTTON_LEFT)) + pl__set_active_id(uHash, ptWindow); +} + +plVec2 +pl_calculate_item_size(float fDefaultHeight) +{ + plUiWindow* ptWindow = gptCtx->ptCurrentWindow; + plUiLayoutRow* ptCurrentRow = &ptWindow->tTempData.tCurrentLayoutRow; + + float fHeight = ptCurrentRow->fHeight; + + if(fHeight == 0.0f) + fHeight = fDefaultHeight; + + if(ptCurrentRow->tSystemType == PL_UI_LAYOUT_SYSTEM_TYPE_TEMPLATE) + { + const plUiLayoutRowEntry* ptCurrentEntry = &ptWindow->sbtRowTemplateEntries[ptCurrentRow->uEntryStartIndex + ptCurrentRow->uCurrentColumn]; + const plVec2 tWidgetSize = { ptCurrentEntry->fWidth, fHeight}; + return tWidgetSize; + } + else + { + // when passed array of sizes/ratios, override + if(ptCurrentRow->pfSizesOrRatios) + ptCurrentRow->fWidth = ptCurrentRow->pfSizesOrRatios[ptCurrentRow->uCurrentColumn]; + + float fWidth = ptCurrentRow->fWidth; + + if(ptCurrentRow->tType == PL_UI_LAYOUT_ROW_TYPE_DYNAMIC) // width was a ratio + { + if(ptWindow->bScrollbarY) + fWidth *= (ptWindow->tSize.x - gptCtx->tStyle.fWindowHorizontalPadding * 2.0f - gptCtx->tStyle.tItemSpacing.x * (float)(ptCurrentRow->uColumns - 1) - 2.0f - gptCtx->tStyle.fScrollbarSize - (float)gptCtx->ptCurrentWindow->tTempData.uTreeDepth * gptCtx->tStyle.fIndentSize); + else + fWidth *= (ptWindow->tSize.x - gptCtx->tStyle.fWindowHorizontalPadding * 2.0f - gptCtx->tStyle.tItemSpacing.x * (float)(ptCurrentRow->uColumns - 1) - (float)gptCtx->ptCurrentWindow->tTempData.uTreeDepth * gptCtx->tStyle.fIndentSize); + } + + const plVec2 tWidgetSize = { fWidth, fHeight}; + return tWidgetSize; + } +} + +void +pl_advance_cursor(float fWidth, float fHeight) +{ + plUiWindow* ptWindow = gptCtx->ptCurrentWindow; + plUiLayoutRow* ptCurrentRow = &ptWindow->tTempData.tCurrentLayoutRow; + + ptCurrentRow->uCurrentColumn++; + + ptCurrentRow->fMaxWidth = pl_maxf(ptCurrentRow->fHorizontalOffset + fWidth, ptCurrentRow->fMaxWidth); + ptCurrentRow->fMaxHeight = pl_maxf(ptCurrentRow->fMaxHeight, ptCurrentRow->fVerticalOffset + fHeight); + + // not yet at end of row + if(ptCurrentRow->uCurrentColumn < ptCurrentRow->uColumns) + ptCurrentRow->fHorizontalOffset += fWidth + gptCtx->tStyle.tItemSpacing.x; + + // automatic wrap + if(ptCurrentRow->uCurrentColumn == ptCurrentRow->uColumns && ptCurrentRow->tSystemType != PL_UI_LAYOUT_SYSTEM_TYPE_ROW_XXX) + { + ptWindow->tTempData.tRowPos.y = ptWindow->tTempData.tRowPos.y + ptCurrentRow->fMaxHeight + gptCtx->tStyle.tItemSpacing.y; + + gptCtx->ptCurrentWindow->tTempData.tCursorMaxPos.x = pl_maxf(ptWindow->tTempData.tRowPos.x + ptCurrentRow->fMaxWidth, gptCtx->ptCurrentWindow->tTempData.tCursorMaxPos.x); + gptCtx->ptCurrentWindow->tTempData.tCursorMaxPos.y = pl_maxf(ptWindow->tTempData.tRowPos.y, gptCtx->ptCurrentWindow->tTempData.tCursorMaxPos.y); + + // reset + ptCurrentRow->uCurrentColumn = 0; + ptCurrentRow->fMaxWidth = 0.0f; + ptCurrentRow->fMaxHeight = 0.0f; + ptCurrentRow->fHorizontalOffset = ptCurrentRow->fRowStartX + ptWindow->tTempData.fExtraIndent; + ptCurrentRow->fVerticalOffset = 0.0f; + } + + // passed end of row + if(ptCurrentRow->uCurrentColumn > ptCurrentRow->uColumns && ptCurrentRow->tSystemType == PL_UI_LAYOUT_SYSTEM_TYPE_ROW_XXX) + { + PL_ASSERT(false); + } +} + +void +pl_submit_window(plUiWindow* ptWindow) +{ + ptWindow->bActive = false; // no longer active (for next frame) + pl_sb_push(gptCtx->sbptWindows, ptWindow); + for(uint32_t j = 0; j < pl_sb_size(ptWindow->sbtChildWindows); j++) + pl_submit_window(ptWindow->sbtChildWindows[j]); +} + +void +pl__set_active_id(uint32_t uHash, plUiWindow* ptWindow) +{ + gptCtx->bActiveIdJustActivated = gptCtx->uActiveId != uHash; + gptCtx->uActiveId = uHash; + gptCtx->ptActiveWindow = ptWindow; + + if(uHash) + gptCtx->uActiveIdIsAlive = uHash; + + if(gptCtx->bActiveIdJustActivated && uHash) + pl__focus_window(ptWindow); +} + +void +pl_ui_initialize(void) +{ + gptCtx->ptDrawlist = gptDraw->request_2d_drawlist(); + gptCtx->ptDebugDrawlist = gptDraw->request_2d_drawlist(); + gptCtx->ptBgLayer = gptDraw->request_2d_layer(gptCtx->ptDrawlist, "ui Background"); + gptCtx->ptFgLayer = gptDraw->request_2d_layer(gptCtx->ptDrawlist, "ui Foreground"); + gptCtx->ptDebugLayer = gptDraw->request_2d_layer(gptCtx->ptDebugDrawlist, "ui debug"); + gptCtx->tTooltipWindow.ptBgLayer = gptDraw->request_2d_layer(gptCtx->ptDrawlist, "Tooltip Background"); + gptCtx->tTooltipWindow.ptFgLayer = gptDraw->request_2d_layer(gptCtx->ptDrawlist, "Tooltip Foreground"); + + gptCtx->tFrameBufferScale.x = 1.0f; + gptCtx->tFrameBufferScale.y = 1.0f; + pl_set_dark_theme(); +} + +void +pl_ui_cleanup(void) +{ + for(uint32_t i = 0; i < pl_sb_size(gptCtx->sbptFocusedWindows); i++) + { + + for(uint32_t j = 0; j < pl_sb_size(gptCtx->sbptFocusedWindows[i]->sbtChildWindows); j++) + { + pl_sb_free(gptCtx->sbptFocusedWindows[i]->sbtChildWindows[j]->tStorage.sbtData); + pl_sb_free(gptCtx->sbptFocusedWindows[i]->sbtChildWindows[j]->sbtRowStack); + pl_sb_free(gptCtx->sbptFocusedWindows[i]->sbtChildWindows[j]->sbtRowTemplateEntries); + pl_sb_free(gptCtx->sbptFocusedWindows[i]->sbtChildWindows[j]->sbtTempLayoutSort); + pl_sb_free(gptCtx->sbptFocusedWindows[i]->sbtChildWindows[j]->sbuTempLayoutIndexSort); + pl_sb_free(gptCtx->sbptFocusedWindows[i]->sbtChildWindows[j]->tStorage.sbtData); + PL_FREE(gptCtx->sbptFocusedWindows[i]->sbtChildWindows[j]); + } + pl_sb_free(gptCtx->sbptFocusedWindows[i]->tStorage.sbtData); + pl_sb_free(gptCtx->sbptFocusedWindows[i]->sbtChildWindows); + pl_sb_free(gptCtx->sbptFocusedWindows[i]->sbtRowStack); + pl_sb_free(gptCtx->sbptFocusedWindows[i]->sbtRowTemplateEntries); + pl_sb_free(gptCtx->sbptFocusedWindows[i]->sbtTempLayoutSort); + pl_sb_free(gptCtx->sbptFocusedWindows[i]->sbuTempLayoutIndexSort); + PL_FREE(gptCtx->sbptFocusedWindows[i]); + } + pl_sb_free(gptCtx->sbptWindows); + pl_sb_free(gptCtx->sbDrawlists); + pl_sb_free(gptCtx->sbptFocusedWindows); + pl_sb_free(gptCtx->sbptWindows); + pl_sb_free(gptCtx->sbtColorStack); + pl_sb_free(gptCtx->sbtTabBars); + pl_sb_free(gptCtx->sbuIdStack); + pl_sb_free(gptCtx->tWindows.sbtData); +} + +//----------------------------------------------------------------------------- +// [SECTION] extension loading +//----------------------------------------------------------------------------- + +#include "pl_ui_widgets.c" +#include "pl_ui_demo.c" + +static const plUiI* +pl_load_ui_api(void) +{ + static const plUiI tApi = { + .initialize = pl_ui_initialize, + .cleanup = pl_ui_cleanup, + .get_draw_list = pl_get_draw_list, + .get_debug_draw_list = pl_get_debug_draw_list, + .new_frame = pl_new_frame, + .end_frame = pl_end_frame, + .render = pl_render, + .show_debug_window = pl_show_debug_window, + .show_style_editor_window = pl_show_style_editor_window, + .show_demo_window = pl_show_demo_window, + .set_dark_theme = pl_set_dark_theme, + .push_theme_color = pl_push_theme_color, + .pop_theme_color = pl_pop_theme_color, + .set_default_font = pl_set_default_font, + .get_default_font = pl_get_default_font, + .begin_window = pl_begin_window, + .end_window = pl_end_window, + .get_window_fg_drawlayer = pl_get_window_fg_drawlayer, + .get_window_bg_drawlayer = pl_get_window_bg_drawlayer, + .get_cursor_pos = pl_get_cursor_pos, + .begin_child = pl_begin_child, + .end_child = pl_end_child, + .begin_tooltip = pl_begin_tooltip, + .end_tooltip = pl_end_tooltip, + .get_window_pos = pl_get_window_pos, + .get_window_size = pl_get_window_size, + .get_window_scroll = pl_get_window_scroll, + .get_window_scroll_max = pl_get_window_scroll_max, + .set_window_scroll = pl_set_window_scroll, + .set_next_window_pos = pl_set_next_window_pos, + .set_next_window_size = pl_set_next_window_size, + .set_next_window_collapse = pl_set_next_window_collapse, + .button = pl_button, + .selectable = pl_selectable, + .checkbox = pl_checkbox, + .radio_button = pl_radio_button, + .image = pl_image, + .image_ex = pl_image_ex, + .image_button = pl_image_button, + .image_button_ex = pl_image_button_ex, + .invisible_button = pl_invisible_button, + .dummy = pl_dummy, + .progress_bar = pl_progress_bar, + .text = pl_text, + .text_v = pl_text_v, + .color_text = pl_color_text, + .color_text_v = pl_color_text_v, + .labeled_text = pl_labeled_text, + .labeled_text_v = pl_labeled_text_v, + .input_text = pl_input_text, + .input_text_hint = pl_input_text_hint, + .input_float = pl_input_float, + .input_int = pl_input_int, + .slider_float = pl_slider_float, + .slider_float_f = pl_slider_float_f, + .slider_int = pl_slider_int, + .slider_int_f = pl_slider_int_f, + .drag_float = pl_drag_float, + .drag_float_f = pl_drag_float_f, + .collapsing_header = pl_collapsing_header, + .end_collapsing_header = pl_end_collapsing_header, + .tree_node = pl_tree_node, + .tree_node_f = pl_tree_node_f, + .tree_node_v = pl_tree_node_v, + .tree_pop = pl_tree_pop, + .begin_tab_bar = pl_begin_tab_bar, + .end_tab_bar = pl_end_tab_bar, + .begin_tab = pl_begin_tab, + .end_tab = pl_end_tab, + .separator = pl_separator, + .vertical_spacing = pl_vertical_spacing, + .indent = pl_indent, + .unindent = pl_unindent, + .step_clipper = pl_step_clipper, + .layout_dynamic = pl_layout_dynamic, + .layout_static = pl_layout_static, + .layout_row_begin = pl_layout_row_begin, + .layout_row_push = pl_layout_row_push, + .layout_row_end = pl_layout_row_end, + .layout_row = pl_layout_row, + .layout_template_begin = pl_layout_template_begin, + .layout_template_push_dynamic = pl_layout_template_push_dynamic, + .layout_template_push_variable = pl_layout_template_push_variable, + .layout_template_push_static = pl_layout_template_push_static, + .layout_template_end = pl_layout_template_end, + .layout_space_begin = pl_layout_space_begin, + .layout_space_push = pl_layout_space_push, + .layout_space_end = pl_layout_space_end, + .was_last_item_hovered = pl_was_last_item_hovered, + .was_last_item_active = pl_was_last_item_active, + }; + return &tApi; +} + +PL_EXPORT void +pl_load_ext(plApiRegistryI* ptApiRegistry, bool bReload) +{ + const plDataRegistryI* ptDataRegistry = ptApiRegistry->first(PL_API_DATA_REGISTRY); + pl_set_memory_context(ptDataRegistry->get_data(PL_CONTEXT_MEMORY)); + + gptIOI = ptApiRegistry->first(PL_API_IO); + gptDraw = ptApiRegistry->first(PL_API_DRAW); + + gptIO = gptIOI->get_io(); + if(bReload) + { + gptCtx = ptDataRegistry->get_data("plUiContext"); + ptApiRegistry->replace(ptApiRegistry->first(PL_API_UI), pl_load_ui_api()); + } + else + { + ptApiRegistry->add(PL_API_UI, pl_load_ui_api()); + + static plUiContext tContext = {0}; + gptCtx = &tContext; + memset(gptCtx, 0, sizeof(plUiContext)); + + ptDataRegistry->set_data("plUiContext", gptCtx); + } +} + +PL_EXPORT void +pl_unload_ext(plApiRegistryI* ptApiRegistry) +{ +} diff --git a/ui/pl_ui_ext.h b/ui/pl_ui_ext.h new file mode 100644 index 00000000..6b88e38d --- /dev/null +++ b/ui/pl_ui_ext.h @@ -0,0 +1,344 @@ +// pilotlight ui + +// library version +#define PL_UI_EXT_VERSION "0.3.2" +#define PL_UI_EXT_VERSION_NUM 000302 + +/* +Index of this file: +// [SECTION] header mess +// [SECTION] includes +// [SECTION] forward declarations +// [SECTION] public api +// [SECTION] enums & flags +// [SECTION] structs +*/ + +//----------------------------------------------------------------------------- +// [SECTION] header mess +//----------------------------------------------------------------------------- + +#ifndef PL_UI_EXT_H +#define PL_UI_EXT_H + +//----------------------------------------------------------------------------- +// [SECTION] includes +//----------------------------------------------------------------------------- + +#include // bool +#include // uint*_t +#include // va list +#include // size_t +#include "pl_math.h" + +//----------------------------------------------------------------------------- +// [SECTION] apis +//----------------------------------------------------------------------------- + +#define PL_API_UI "PL_API_UI" +typedef struct _plUiI plUiI; + +//----------------------------------------------------------------------------- +// [SECTION] forward declarations +//----------------------------------------------------------------------------- + +// basic types +typedef struct _plUiContext plUiContext; // (opaque structure) +typedef struct _plUiClipper plUiClipper; // data used with "pl_step_clipper(...)" function (see function) + +// enums +typedef int plUiConditionFlags; // -> enum plUiConditionFlags_ // Enum: A conditional for some functions (PL_UI_COND_XXX value) +typedef int plUiLayoutRowType; // -> enum plUiLayoutRowType_ // Enum: A row type for the layout system (PL_UI_LAYOUT_ROW_TYPE_XXX) +typedef int plUiInputTextFlags; // -> enum plUiInputTextFlags_ // Enum: Internal flags for input text (PL_UI_INPUT_TEXT_FLAGS_XXX) +typedef int plUiWindowFlags; // -> enum plUiWindowFlags_ // Enum: An input event source (PL_UI_WINDOW_FLAGS_XXXX) +typedef int plUiColor; // -> enum plUiColor_ // Enum: An input event source (PL_UI_COLOR_XXXX) + +// external +typedef struct _plDrawList2D plDrawList2D; // pl_draw_ext.h +typedef struct _plDrawLayer2D plDrawLayer2D; // pl_draw_ext.h +typedef struct _plFont plFont; // pl_draw_ext.h +typedef struct _plRenderEncoder plRenderEncoder; // pl_graphics_ext.h +typedef union plTextureHandle plTextureHandle; // pl_graphics_ext.h + +//----------------------------------------------------------------------------- +// [SECTION] public api struct +//----------------------------------------------------------------------------- + +typedef struct _plUiI +{ + + void (*initialize)(void); + void (*cleanup)(void); + + // render data + plDrawList2D* (*get_draw_list) (void); + plDrawList2D* (*get_debug_draw_list)(void); + + // main + void (*new_frame)(void); // start a new pilotlight ui frame, this should be the first command before calling any commands below + void (*end_frame)(void); // ends pilotlight ui frame, automatically called by pl_render() + void (*render)(plRenderEncoder, float fWidth, float fHeight, uint32_t uMSAASampleCount); // submits draw layers, you can then submit the ptDrawlist & ptDebugDrawlist from context + + // tools + void (*show_debug_window) (bool* pbOpen); + void (*show_style_editor_window)(bool* pbOpen); + void (*show_demo_window) (bool* pbOpen); + + // styling + void (*set_dark_theme) (void); + void (*push_theme_color)(plUiColor tColor, const plVec4* ptColor); + void (*pop_theme_color) (uint32_t uCount); + + // fonts + void (*set_default_font)(plFont* ptFont); + plFont* (*get_default_font)(void); + + //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~windows~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + // windows + // - only call "pl_end_window()" if "pl_begin_window()" returns true (its call automatically if false) + // - passing a valid pointer to pbOpen will show a red circle that will turn pbOpen false when clicked + // - "pl_end_window()" will return false if collapsed or clipped + // - if you use autosize, make sure at least 1 row has a static component (or the window will grow unbounded) + bool (*begin_window)(const char* pcName, bool* pbOpen, plUiWindowFlags); + void (*end_window) (void); + + // window utilities + plDrawLayer2D* (*get_window_fg_drawlayer)(void); // returns current window foreground drawlist (call between pl_begin_window(...) & pl_end_window(...)) + plDrawLayer2D* (*get_window_bg_drawlayer)(void); // returns current window background drawlist (call between pl_begin_window(...) & pl_end_window(...)) + plVec2 (*get_cursor_pos)(void); // returns current cursor position (where the next widget will start drawing) + + // child windows + // - only call "pl_end_child()" if "pl_begin_child()" returns true (its call automatically if false) + // - self-contained window with scrolling & clipping + bool (*begin_child)(const char* pcName); + void (*end_child) (void); + + // tooltips + // - window that follows the mouse (usually used in combination with "pl_was_last_item_hovered()") + void (*begin_tooltip)(void); + void (*end_tooltip) (void); + + // window utilities + // - refers to current window (between "pl_begin_window()" & "pl_end_window()") + plVec2 (*get_window_pos) (void); + plVec2 (*get_window_size) (void); + plVec2 (*get_window_scroll) (void); + plVec2 (*get_window_scroll_max)(void); + void (*set_window_scroll) (plVec2 tScroll); + + // window manipulation + // - call before "pl_begin_window()" + void (*set_next_window_pos) (plVec2 tPos, plUiConditionFlags); + void (*set_next_window_size) (plVec2 tSize, plUiConditionFlags); + void (*set_next_window_collapse)(bool bCollapsed, plUiConditionFlags); + + //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~widgets~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + // main + bool (*button) (const char* pcText); + bool (*selectable) (const char* pcText, bool* bpValue); + bool (*checkbox) (const char* pcText, bool* pbValue); + bool (*radio_button) (const char* pcText, int* piValue, int iButtonValue); + void (*image) (plTextureHandle tTexture, plVec2 tSize); + void (*image_ex) (plTextureHandle tTexture, plVec2 tSize, plVec2 tUv0, plVec2 tUv1, plVec4 tTintColor, plVec4 tBorderColor); + bool (*image_button) (const char* pcId, plTextureHandle tTexture, plVec2 tSize); + bool (*image_button_ex) (const char* pcId, plTextureHandle tTexture, plVec2 tSize, plVec2 tUv0, plVec2 tUv1, plVec4 tTintColor, plVec4 tBorderColor); + bool (*invisible_button)(const char* pcText, plVec2 tSize); + void (*dummy) (plVec2 tSize); + + // plotting + void (*progress_bar)(float fFraction, plVec2 tSize, const char* pcOverlay); + + // text + void (*text) (const char* pcFmt, ...); + void (*text_v) (const char* pcFmt, va_list args); + void (*color_text) (plVec4 tColor, const char* pcFmt, ...); + void (*color_text_v) (plVec4 tColor, const char* pcFmt, va_list args); + void (*labeled_text) (const char* pcLabel, const char* pcFmt, ...); + void (*labeled_text_v)(const char* pcLabel, const char* pcFmt, va_list args); + + // input + bool (*input_text) (const char* pcLabel, char* pcBuffer, size_t szBufferSize); + bool (*input_text_hint)(const char* pcLabel, const char* pcHint, char* pcBuffer, size_t szBufferSize); + bool (*input_float) (const char* pcLabel, float* fValue, const char* pcFormat); + bool (*input_int) (const char* pcLabel, int* iValue); + + // sliders + bool (*slider_float) (const char* pcLabel, float* pfValue, float fMin, float fMax); + bool (*slider_float_f)(const char* pcLabel, float* pfValue, float fMin, float fMax, const char* pcFormat); + bool (*slider_int) (const char* pcLabel, int* piValue, int iMin, int iMax); + bool (*slider_int_f) (const char* pcLabel, int* piValue, int iMin, int iMax, const char* pcFormat); + + // drag sliders + bool (*drag_float) (const char* pcLabel, float* pfValue, float fSpeed, float fMin, float fMax); + bool (*drag_float_f)(const char* pcLabel, float* pfValue, float fSpeed, float fMin, float fMax, const char* pcFormat); + + // trees + // - only call "pl_tree_pop()" if "pl_tree_node()" returns true (its call automatically if false) + // - only call "pl_end_collapsing_header()" if "pl_collapsing_header()" returns true (its call automatically if false) + bool (*collapsing_header) (const char* pcText); + void (*end_collapsing_header)(void); + bool (*tree_node) (const char* pcText); + bool (*tree_node_f) (const char* pcFmt, ...); + bool (*tree_node_v) (const char* pcFmt, va_list args); + void (*tree_pop) (void); + + // tabs & tab bars + // - only call "pl_end_tab_bar()" if "pl_begin_tab_bar()" returns true (its call automatically if false) + // - only call "pl_end_tab()" if "pl_begin_tab()" returns true (its call automatically if false) + bool (*begin_tab_bar)(const char* pcText); + void (*end_tab_bar) (void); + bool (*begin_tab) (const char* pcText); + void (*end_tab) (void); + + // misc. + void (*separator) (void); + void (*vertical_spacing)(void); + void (*indent) (float fIndent); + void (*unindent) (float fIndent); + + //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~clipper~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + // - clipper based on "Dear ImGui"'s ImGuiListClipper (https://github.com/ocornut/imgui) + // - Used for large numbers of evenly spaced rows. + // - Without Clipper: + // for(uint32_t i = 0; i < QUANTITY; i++) + // ptUi->text("%i", i); + // - With Clipper: + // plUiClipper tClipper = {QUANTITY}; + // while(ptUi->step_clipper(&tClipper)) + // for(uint32_t i = tClipper.uDisplayStart; i < tClipper.uDisplayEnd; i++) + // ptUi->text("%i", i); + bool (*step_clipper)(plUiClipper* ptClipper); + + //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~layout systems~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + // - layout systems are based on "Nuklear" (https://github.com/Immediate-Mode-UI/Nuklear) + // - 6 different layout strategies + // - default layout strategy is system 2 with uWidgetCount=1 & fWidth=300 & fHeight=0 + // - setting fHeight=0 will cause the row height to be equal to the minimal height of the maximum height widget + + // layout system 1 + // - provides each widget with the same horizontal space and grows dynamically with the parent window + // - wraps (i.e. setting uWidgetCount to 2 and adding 4 widgets will create 2 rows) + void (*layout_dynamic)(float fHeight, uint32_t uWidgetCount); + + // layout system 2 + // - provides each widget with the same horizontal pixel widget and does not grow with the parent window + // - wraps (i.e. setting uWidgetCount to 2 and adding 4 widgets will create 2 rows) + void (*layout_static)(float fHeight, float fWidth, uint32_t uWidgetCount); + + // layout system 3 + // - allows user to change the width per widget + // - if tType=PL_UI_LAYOUT_ROW_TYPE_STATIC, then fWidth is pixel width + // - if tType=PL_UI_LAYOUT_ROW_TYPE_DYNAMIC, then fWidth is a ratio of the available width + // - does not wrap + void (*layout_row_begin)(plUiLayoutRowType tType, float fHeight, uint32_t uWidgetCount); + void (*layout_row_push) (float fWidth); + void (*layout_row_end) (void); + + // layout system 4 + // - same as layout system 3 but the "array" form + // - wraps (i.e. setting uWidgetCount to 2 and adding 4 widgets will create 2 rows) + void (*layout_row)(plUiLayoutRowType tType, float fHeight, uint32_t uWidgetCount, const float* pfSizesOrRatios); + + // layout system 5 + // - similar to a flexbox + // - wraps (i.e. setting uWidgetCount to 2 and adding 4 widgets will create 2 rows) + void (*layout_template_begin) (float fHeight); + void (*layout_template_push_dynamic) (void); // can go to minimum widget width if not enough space (10 pixels) + void (*layout_template_push_variable)(float fWidth); // variable width with min pixel width of fWidth but can grow bigger if enough space + void (*layout_template_push_static) (float fWidth); // static pixel width of fWidth + void (*layout_template_end) (void); + + // layout system 6 + // - allows user to place widgets freely + // - if tType=PL_UI_LAYOUT_ROW_TYPE_STATIC, then fWidth/fHeight is pixel width/height + // - if tType=PL_UI_LAYOUT_ROW_TYPE_DYNAMIC, then fWidth/fHeight is a ratio of the available width/height (for pl_layout_space_begin()) + // - if tType=PL_UI_LAYOUT_ROW_TYPE_DYNAMIC, then fWidth is a ratio of the available width & fHeight is a ratio of fHeight given to "pl_layout_space_begin()" (for pl_layout_space_push()) + void (*layout_space_begin)(plUiLayoutRowType tType, float fHeight, uint32_t uWidgetCount); + void (*layout_space_push) (float fX, float fY, float fWidth, float fHeight); + void (*layout_space_end) (void); + + //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~state query~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + bool (*was_last_item_hovered)(void); + bool (*was_last_item_active) (void); + +} plUiI; + + +//----------------------------------------------------------------------------- +// [SECTION] enums & flags +//----------------------------------------------------------------------------- + +enum plUiColor_ +{ + PL_UI_COLOR_TITLE_ACTIVE, + PL_UI_COLOR_TITLE_BG, + PL_UI_COLOR_TITLE_BG_COLLAPSED, + PL_UI_COLOR_WINDOW_BG, + PL_UI_COLOR_WINDOW_BORDER, + PL_UI_COLOR_CHILD_BG, + PL_UI_COLOR_BUTTON, + PL_UI_COLOR_BUTTON_HOVERED, + PL_UI_COLOR_BUTTON_ACTIVE, + PL_UI_COLOR_TEXT, + PL_UI_COLOR_PROGRESS_BAR, + PL_UI_COLOR_CHECKMARK, + PL_UI_COLOR_FRAME_BG, + PL_UI_COLOR_FRAME_BG_HOVERED, + PL_UI_COLOR_FRAME_BG_ACTIVE, + PL_UI_COLOR_HEADER, + PL_UI_COLOR_HEADER_HOVERED, + PL_UI_COLOR_HEADER_ACTIVE, + PL_UI_COLOR_SCROLLBAR_BG, + PL_UI_COLOR_SCROLLBAR_HANDLE, + PL_UI_COLOR_SCROLLBAR_FRAME, + PL_UI_COLOR_SCROLLBAR_ACTIVE, + PL_UI_COLOR_SCROLLBAR_HOVERED +}; + +enum plUiWindowFlags_ +{ + PL_UI_WINDOW_FLAGS_NONE = 0, + PL_UI_WINDOW_FLAGS_NO_TITLE_BAR = 1 << 0, + PL_UI_WINDOW_FLAGS_NO_RESIZE = 1 << 1, + PL_UI_WINDOW_FLAGS_NO_MOVE = 1 << 2, + PL_UI_WINDOW_FLAGS_NO_COLLAPSE = 1 << 3, + PL_UI_WINDOW_FLAGS_AUTO_SIZE = 1 << 4, + + // internal + PL_UI_WINDOW_FLAGS_CHILD_WINDOW = 1 << 5, + PL_UI_WINDOW_FLAGS_TOOLTIP = 1 << 6, +}; + +enum plUiConditionFlags_ +{ + PL_UI_COND_NONE = 0, + PL_UI_COND_ALWAYS = 1 << 0, + PL_UI_COND_ONCE = 1 << 1 +}; + +enum plUiLayoutRowType_ +{ + PL_UI_LAYOUT_ROW_TYPE_NONE, // don't use (internal only) + PL_UI_LAYOUT_ROW_TYPE_DYNAMIC, + PL_UI_LAYOUT_ROW_TYPE_STATIC +}; + +//----------------------------------------------------------------------------- +// [SECTION] structs +//----------------------------------------------------------------------------- + +typedef struct _plUiClipper +{ + uint32_t uItemCount; + uint32_t uDisplayStart; + uint32_t uDisplayEnd; + float _fItemHeight; + float _fStartPosY; +} plUiClipper; + +#endif // PL_UI_EXT_H \ No newline at end of file diff --git a/ui/pl_ui_internal.h b/ui/pl_ui_internal.h new file mode 100644 index 00000000..61b8472d --- /dev/null +++ b/ui/pl_ui_internal.h @@ -0,0 +1,532 @@ +/* + pl_ui_internal.h + - FORWARD COMPATIBILITY NOT GUARANTEED +*/ + +/* +Index of this file: +// [SECTION] header mess +// [SECTION] includes +// [SECTION] helper macros +// [SECTION] defines +// [SECTION] forward declarations +// [SECTION] context +// [SECTION] enums +// [SECTION] math +// [SECTION] stretchy buffer internal +// [SECTION] stretchy buffer +// [SECTION] internal structs +// [SECTION] plUiStorage +// [SECTION] plUiWindow +// [SECTION] plUiContext +// [SECTION] internal api +*/ + +//----------------------------------------------------------------------------- +// [SECTION] header mess +//----------------------------------------------------------------------------- + +#ifndef PL_UI_INTERNAL_H +#define PL_UI_INTERNAL_H + +//----------------------------------------------------------------------------- +// [SECTION] includes +//----------------------------------------------------------------------------- + +#include // uint32_t +#include // malloc, free +#include // memset, memmove +#include // bool +#include // arg vars +#include // vsprintf +#include +#include +#include "pl_string.h" +#include "pl_ui_ext.h" +#include "pl_draw_ext.h" +#define PL_MATH_INCLUDE_FUNCTIONS +#include "pl_math.h" + +//----------------------------------------------------------------------------- +// [SECTION] helper macros +//----------------------------------------------------------------------------- + +// stb +#undef STB_TEXTEDIT_STRING +#undef STB_TEXTEDIT_CHARTYPE +#define STB_TEXTEDIT_STRING plUiInputTextState +#define STB_TEXTEDIT_CHARTYPE plUiWChar +#define STB_TEXTEDIT_GETWIDTH_NEWLINE (-1.0f) +#define STB_TEXTEDIT_UNDOSTATECOUNT 99 +#define STB_TEXTEDIT_UNDOCHARCOUNT 999 +#include "stb_textedit.h" + +//----------------------------------------------------------------------------- +// [SECTION] defines +//----------------------------------------------------------------------------- + +// Helper: Unicode defines +#define PL_UNICODE_CODEPOINT_INVALID 0xFFFD // Invalid Unicode code point (standard value). +#define PL_UNICODE_CODEPOINT_MAX 0xFFFF // Maximum Unicode code point supported by this build. + +#define PLU_PI_2 1.57079632f // pi/2 +#define PLU_2PI 6.28318530f // pi + +//----------------------------------------------------------------------------- +// [SECTION] forward declarations +//----------------------------------------------------------------------------- + +// basic types +typedef struct _plUiStyle plUiStyle; +typedef union _plUiColorScheme plUiColorScheme; +typedef struct _plUiColorStackItem plUiColorStackItem; +typedef struct _plUiWindow plUiWindow; +typedef struct _plUiTabBar plUiTabBar; +typedef struct _plUiPrevItemData plUiPrevItemData; +typedef struct _plUiNextWindowData plUiNextWindowData; +typedef struct _plUiTempWindowData plUiTempWindowData; +typedef struct _plUiStorage plUiStorage; +typedef struct _plUiStorageEntry plUiStorageEntry; +typedef struct _plUiInputTextState plUiInputTextState; + +// enums +typedef int plUiNextWindowFlags; +typedef int plUiAxis; +typedef int plUiLayoutRowEntryType; +typedef int plUiLayoutSystemType; +typedef int plDebugLogFlags; + +//----------------------------------------------------------------------------- +// [SECTION] context +//----------------------------------------------------------------------------- + +plUiContext* gptCtx = NULL; +const plIOI* gptIOI = NULL; +const plDrawI* gptDraw = NULL; +plIO* gptIO = NULL; + +//----------------------------------------------------------------------------- +// [SECTION] enums +//----------------------------------------------------------------------------- + +enum plDebugLogFlags_ +{ + PL_UI_DEBUG_LOG_FLAGS_NONE = 0, + PL_UI_DEBUG_LOG_FLAGS_EVENT_IO = 1 << 0, + PL_UI_DEBUG_LOG_FLAGS_EVENT_ACTIVE_ID = 1 << 1, +}; + +enum plUiAxis_ +{ + PL_UI_AXIS_NONE = -1, + PL_UI_AXIS_X = 0, + PL_UI_AXIS_Y = 1, +}; + +enum _plUiNextWindowFlags +{ + PL_NEXT_WINDOW_DATA_FLAGS_NONE = 0, + PL_NEXT_WINDOW_DATA_FLAGS_HAS_POS = 1 << 0, + PL_NEXT_WINDOW_DATA_FLAGS_HAS_SIZE = 1 << 1, + PL_NEXT_WINDOW_DATA_FLAGS_HAS_COLLAPSED = 1 << 2, +}; + +enum plUiLayoutRowEntryType_ +{ + PL_UI_LAYOUT_ROW_ENTRY_TYPE_NONE, + PL_UI_LAYOUT_ROW_ENTRY_TYPE_VARIABLE, + PL_UI_LAYOUT_ROW_ENTRY_TYPE_DYNAMIC, + PL_UI_LAYOUT_ROW_ENTRY_TYPE_STATIC +}; + +enum plUiLayoutSystemType_ +{ + PL_UI_LAYOUT_SYSTEM_TYPE_NONE, + PL_UI_LAYOUT_SYSTEM_TYPE_DYNAMIC, + PL_UI_LAYOUT_SYSTEM_TYPE_STATIC, + PL_UI_LAYOUT_SYSTEM_TYPE_SPACE, + PL_UI_LAYOUT_SYSTEM_TYPE_ARRAY, + PL_UI_LAYOUT_SYSTEM_TYPE_TEMPLATE, + PL_UI_LAYOUT_SYSTEM_TYPE_ROW_XXX +}; + +enum plUiInputTextFlags_ // borrowed from "Dear ImGui" +{ + PL_UI_INPUT_TEXT_FLAGS_NONE = 0, + PL_UI_INPUT_TEXT_FLAGS_CHARS_DECIMAL = 1 << 0, // allow 0123456789.+-*/ + PL_UI_INPUT_TEXT_FLAGS_CHARS_HEXADECIMAL = 1 << 1, // allow 0123456789ABCDEFabcdef + PL_UI_INPUT_TEXT_FLAGS_CHARS_UPPERCASE = 1 << 2, // turn a..z into A..Z + PL_UI_INPUT_TEXT_FLAGS_CHARS_NO_BLANK = 1 << 3, // filter out spaces, tabs + PL_UI_INPUT_TEXT_FLAGS_AUTO_SELECT_ALL = 1 << 4, // select entire text when first taking mouse focus + PL_UI_INPUT_TEXT_FLAGS_ENTER_RETURNS_TRUE = 1 << 5, // return 'true' when Enter is pressed (as opposed to every time the value was modified). Consider looking at the IsItemDeactivatedAfterEdit() function. + PL_UI_INPUT_TEXT_FLAGS_CALLBACK_COMPLETION = 1 << 6, // callback on pressing TAB (for completion handling) + PL_UI_INPUT_TEXT_FLAGS_CALLBACK_HISTORY = 1 << 7, // callback on pressing Up/Down arrows (for history handling) + PL_UI_INPUT_TEXT_FLAGS_CALLBACK_ALWAYS = 1 << 8, // callback on each iteration. User code may query cursor position, modify text buffer. + PL_UI_INPUT_TEXT_FLAGS_CALLBACK_CHAR_FILTER = 1 << 9, // callback on character inputs to replace or discard them. Modify 'EventChar' to replace or discard, or return 1 in callback to discard. + PL_UI_INPUT_TEXT_FLAGS_ALLOW_TAB_INPUT = 1 << 10, // pressing TAB input a '\t' character into the text field + PL_UI_INPUT_TEXT_FLAGS_CTRL_ENTER_FOR_NEW_LINE = 1 << 11, // in multi-line mode, unfocus with Enter, add new line with Ctrl+Enter (default is opposite: unfocus with Ctrl+Enter, add line with Enter). + PL_UI_INPUT_TEXT_FLAGS_NO_HORIZONTAL_SCROLL = 1 << 12, // disable following the cursor horizontally + PL_UI_INPUT_TEXT_FLAGS_ALWAYS_OVERWRITE = 1 << 13, // overwrite mode + PL_UI_INPUT_TEXT_FLAGS_READ_ONLY = 1 << 14, // read-only mode + PL_UI_INPUT_TEXT_FLAGS_PASSWORD = 1 << 15, // password mode, display all characters as '*' + PL_UI_INPUT_TEXT_FLAGS_NO_UNDO_REDO = 1 << 16, // disable undo/redo. Note that input text owns the text data while active, if you want to provide your own undo/redo stack you need e.g. to call ClearActiveID(). + PL_UI_INPUT_TEXT_FLAGS_CHARS_SCIENTIFIC = 1 << 17, // allow 0123456789.+-*/eE (Scientific notation input) + PL_UI_INPUT_TEXT_FLAGS_CALLBACK_RESIZE = 1 << 18, // callback on buffer capacity changes request (beyond 'buf_size' parameter value), allowing the string to grow. Notify when the string wants to be resized (for string types which hold a cache of their Size). You will be provided a new BufSize in the callback and NEED to honor it. (see misc/cpp/imgui_stdlib.h for an example of using this) + PL_UI_INPUT_TEXT_FLAGS_CALLBACK_EDIT = 1 << 19, // callback on any edit (note that InputText() already returns true on edit, the callback is useful mainly to manipulate the underlying buffer while focus is active) + PL_UI_INPUT_TEXT_FLAGS_ESCAPE_CLEARS_ALL = 1 << 20, // escape key clears content if not empty, and deactivate otherwise (contrast to default behavior of Escape to revert) + + // internal + PL_UI_INPUT_TEXT_FLAGS_MULTILINE = 1 << 21, // escape key clears content if not empty, and deactivate otherwise (contrast to default behavior of Escape to revert) + PL_UI_INPUT_TEXT_FLAGS_NO_MARK_EDITED = 1 << 22, // escape key clears content if not empty, and deactivate otherwise (contrast to default behavior of Escape to revert) +}; + +#define PLU_VEC2_LENGTH_SQR(vec) (((vec).x * (vec).x) + ((vec).y * (vec).y)) + +//----------------------------------------------------------------------------- +// [SECTION] internal structs +//----------------------------------------------------------------------------- + +typedef struct _plUiColorStackItem +{ + plUiColor tIndex; + plVec4 tColor; +} plUiColorStackItem; + +typedef union _plUiColorScheme +{ + struct + { + plVec4 tTitleActiveCol; + plVec4 tTitleBgCol; + plVec4 tTitleBgCollapsedCol; + plVec4 tWindowBgColor; + plVec4 tWindowBorderColor; + plVec4 tChildBgColor; + plVec4 tButtonCol; + plVec4 tButtonHoveredCol; + plVec4 tButtonActiveCol; + plVec4 tTextCol; + plVec4 tProgressBarCol; + plVec4 tCheckmarkCol; + plVec4 tFrameBgCol; + plVec4 tFrameBgHoveredCol; + plVec4 tFrameBgActiveCol; + plVec4 tHeaderCol; + plVec4 tHeaderHoveredCol; + plVec4 tHeaderActiveCol; + plVec4 tScrollbarBgCol; + plVec4 tScrollbarHandleCol; + plVec4 tScrollbarFrameCol; + plVec4 tScrollbarActiveCol; + plVec4 tScrollbarHoveredCol; + }; + plVec4 atColors[23]; +} plUiColorScheme; + +typedef struct _plUiStyle +{ + // style + float fTitlePadding; + float fFontSize; + float fIndentSize; + float fWindowHorizontalPadding; + float fWindowVerticalPadding; + float fScrollbarSize; + float fSliderSize; + plVec2 tItemSpacing; + plVec2 tInnerSpacing; + plVec2 tFramePadding; +} plUiStyle; + +typedef struct _plUiTabBar +{ + uint32_t uId; + plVec2 tStartPos; + plVec2 tCursorPos; + uint32_t uCurrentIndex; + uint32_t uValue; + uint32_t uNextValue; +} plUiTabBar; + +typedef struct _plUiPrevItemData +{ + bool bHovered; + bool bActive; +} plUiPrevItemData; + +typedef struct _plUiLayoutSortLevel +{ + float fWidth; + uint32_t uStartIndex; + uint32_t uCount; +} plUiLayoutSortLevel; + +typedef struct _plUiLayoutRowEntry +{ + plUiLayoutRowEntryType tType; // entry type (PL_UI_LAYOUT_ROW_ENTRY_TYPE_*) + float fWidth; // widget width (could be relative or absolute) +} plUiLayoutRowEntry; + +typedef struct _plUiLayoutRow +{ + plUiLayoutRowType tType; // determines if width/height is relative or absolute (PL_UI_LAYOUT_ROW_TYPE_*) + plUiLayoutSystemType tSystemType; // this rows layout strategy + + float fWidth; // widget width (could be relative or absolute) + float fHeight; // widget height (could be relative or absolute) + float fSpecifiedHeight; // height user passed in + float fVerticalOffset; // used by space layout system (system 6) to temporary offset widget placement + float fHorizontalOffset; // offset where the next widget should start from (usually previous widget + item spacing) + uint32_t uColumns; // number of columns in row + uint32_t uCurrentColumn; // current column + float fMaxWidth; // maximum row width (to help set max cursor position) + float fMaxHeight; // maximum row height (to help set next row position + max cursor position) + float fRowStartX; // offset where the row starts from (think of a tab bar being the second widget in the row) + const float* pfSizesOrRatios; // size or rations when using array layout system (system 4) + uint32_t uStaticEntryCount; // number of static entries when using template layout system (system 5) + uint32_t uDynamicEntryCount; // number of dynamic entries when using template layout system (system 5) + uint32_t uVariableEntryCount; // number of variable entries when using template layout system (system 5) + uint32_t uEntryStartIndex; // offset into parent window sbtRowTemplateEntries buffer +} plUiLayoutRow; + +typedef struct _plUiInputTextState +{ + uint32_t uId; + int iCurrentLengthW; // widget id owning the text state + int iCurrentLengthA; // we need to maintain our buffer length in both UTF-8 and wchar format. UTF-8 length is valid even if TextA is not. + plUiWChar* sbTextW; // edit buffer, we need to persist but can't guarantee the persistence of the user-provided buffer. so we copy into own buffer. + char* sbTextA; // temporary UTF8 buffer for callbacks and other operations. this is not updated in every code-path! size=capacity. + char* sbInitialTextA; // backup of end-user buffer at the time of focus (in UTF-8, unaltered) + bool bTextAIsValid; // temporary UTF8 buffer is not initially valid before we make the widget active (until then we pull the data from user argument) + int iBufferCapacityA; // end-user buffer capacity + float fScrollX; // horizontal scrolling/offset + STB_TexteditState tStb; // state for stb_textedit.h + float fCursorAnim; // timer for cursor blink, reset on every user action so the cursor reappears immediately + bool bCursorFollow; // set when we want scrolling to follow the current cursor position (not always!) + bool bSelectedAllMouseLock; // after a double-click to select all, we ignore further mouse drags to update selection + bool bEdited; // edited this frame + plUiInputTextFlags tFlags; // copy of InputText() flags. may be used to check if e.g. ImGuiInputTextFlags_Password is set. +} plUiInputTextState; + +//----------------------------------------------------------------------------- +// [SECTION] plUiStorage +//----------------------------------------------------------------------------- + +typedef struct _plUiStorageEntry +{ + uint32_t uKey; + union + { + int iValue; + float fValue; + void* pValue; + }; +} plUiStorageEntry; + +typedef struct _plUiStorage +{ + plUiStorageEntry* sbtData; +} plUiStorage; + +//----------------------------------------------------------------------------- +// [SECTION] plUiWindow +//----------------------------------------------------------------------------- + +typedef struct _plUiTempWindowData +{ + plVec2 tCursorStartPos; // position where widgets begin drawing (could be outside window if scrolling) + plVec2 tCursorMaxPos; // maximum cursor position (could be outside window if scrolling) + uint32_t uTreeDepth; // current level inside trees + float fExtraIndent; // extra indent added by pl_indent + plUiLayoutRow tCurrentLayoutRow; // current layout row to use + plVec2 tRowPos; // current row starting position + float fAccumRowX; // additional indent due to a parent (like tab bar) not being the first item in a row + float fTitleBarHeight; // titlebar height + + // template layout system + float fTempMinWidth; + float fTempStaticWidth; +} plUiTempWindowData; + +typedef struct _plUiWindow +{ + const char* pcName; + uint32_t uId; // window Id (=plu_str_hash(pcName)) + plUiWindowFlags tFlags; // plUiWindowFlags, not all honored at the moment + plVec2 tPos; // position of window in viewport + plVec2 tContentSize; // size of contents/scrollable client area + plVec2 tMinSize; // minimum size of window (default 200,200) + plVec2 tMaxSize; // maximum size of window (default 10000,10000) + plVec2 tSize; // full size or title bar size if collapsed + plVec2 tFullSize; // used to restore size after uncollapsing + plVec2 tScroll; // current scroll amount (0 < tScroll < tScrollMax) + plVec2 tScrollMax; // maximum scroll amount based on last frame content size & adjusted for scroll bars + plRect tInnerRect; // inner rect (excludes titlebar & scrollbars) + plRect tOuterRect; // outer rect (includes everything) + plRect tOuterRectClipped; // outer rect clipped by parent window & viewport + plRect tInnerClipRect; // inner rect clipped by parent window & viewport (includes horizontal padding on each side) + plUiWindow* ptParentWindow; // parent window if child + plUiWindow* ptRootWindow; // root window or self if this is the root window + bool bVisible; // true if visible (only for child windows at the moment) + bool bActive; // window has been "seen" this frame + bool bCollapsed; // window is currently collapsed + bool bScrollbarX; // set if horizontal scroll bar is "on" + bool bScrollbarY; // set if vertical scroll bar is "on" + plUiTempWindowData tTempData; // temporary data reset at the beginning of frame + plUiWindow** sbtChildWindows; // child windows if any (reset every frame) + plUiLayoutRow* sbtRowStack; // row stack for containers to push parents row onto and pop when they exist (reset every frame) + plUiLayoutSortLevel* sbtTempLayoutSort; // blah + uint32_t* sbuTempLayoutIndexSort; // blah + plUiLayoutRowEntry* sbtRowTemplateEntries; // row template entries (shared and reset every frame) + plDrawLayer2D* ptBgLayer; // background draw layer + plDrawLayer2D* ptFgLayer; // foreground draw layer + plUiConditionFlags tPosAllowableFlags; // acceptable condition flags for "pl_set_next_window_pos()" + plUiConditionFlags tSizeAllowableFlags; // acceptable condition flags for "pl_set_next_window_size()" + plUiConditionFlags tCollapseAllowableFlags; // acceptable condition flags for "pl_set_next_window_collapse()" + uint8_t uHideFrames; // hide window for this many frames (useful for autosizing) + uint32_t uFocusOrder; // display rank + plUiStorage tStorage; // state storage +} plUiWindow; + +//----------------------------------------------------------------------------- +// [SECTION] plUiContext +//----------------------------------------------------------------------------- + +typedef struct _plUiNextWindowData +{ + plUiNextWindowFlags tFlags; + plUiConditionFlags tPosCondition; + plUiConditionFlags tSizeCondition; + plUiConditionFlags tCollapseCondition; + plVec2 tPos; + plVec2 tSize; + bool bCollapsed; +} plUiNextWindowData; + +typedef struct _plUiContext +{ + plUiStyle tStyle; + plUiColorScheme tColorScheme; + plIO tIO; + + // prev/next state + plUiNextWindowData tNextWindowData; // info based on pl_set_next_window_* functions + plUiPrevItemData tPrevItemData; // data for use by pl_was_last_item_* functions + uint32_t* sbuIdStack; // id stack for hashing IDs, container items usually push/pop these + + // widget state + plUiInputTextState tInputTextState; + uint32_t uHoveredId; // set at the end of the previous frame from uNextHoveredId + uint32_t uActiveId; // set at the end of the previous frame from uNextActiveId + uint32_t uActiveIdIsAlive; + uint32_t uNextHoveredId; // set during current frame (by end of frame, should be last item hovered) + + uint32_t uActiveWindowId; // current active window id + bool bActiveIdJustActivated; // window was just activated, so bring it to the front + + // windows + plUiWindow tTooltipWindow; // persistent tooltip window (since there can only ever be 1 at a time) + plUiWindow* ptCurrentWindow; // current window we are appending into + plUiWindow* ptHoveredWindow; // window being hovered + plUiWindow* ptMovingWindow; // window being moved + plUiWindow* ptSizingWindow; // window being resized + plUiWindow* ptScrollingWindow; // window being scrolled with mouse + plUiWindow* ptWheelingWindow; // window being scrolled with mouse wheel + plUiWindow* ptActiveWindow; // selected/active window + plUiWindow** sbptWindows; // windows stored in display order (reset every frame and non root windows) + plUiWindow** sbptFocusedWindows; // root windows stored in display order + plUiStorage tWindows; // windows by ID for quick retrieval + + // tabs + plUiTabBar* sbtTabBars; // stretchy-buffer for persistent tab bar data + plUiTabBar* ptCurrentTabBar; // current tab bar being appended to + + // theme stacks + plUiColorStackItem* sbtColorStack; + + // drawing + plDrawList2D* ptDrawlist; // main ui drawlist + plDrawList2D* ptDebugDrawlist; // ui debug drawlist (i.e. overlays) + plFont* ptFont; // current font + plDrawLayer2D* ptBgLayer; // submitted before window layers + plDrawLayer2D* ptFgLayer; // submitted after window layers + plDrawLayer2D* ptDebugLayer; // submitted last + + // drawing context + plDrawList2D** sbDrawlists; + plFontAtlas* fontAtlas; + plVec2 tFrameBufferScale; +} plUiContext; + +//----------------------------------------------------------------------------- +// [SECTION] internal api +//----------------------------------------------------------------------------- + +const char* pl_find_renderered_text_end(const char* pcText, const char* pcTextEnd); +void pl_ui_add_text (plDrawLayer2D* ptLayer, plFont* ptFont, float fSize, plVec2 tP, plVec4 tColor, const char* pcText, float fWrap); +void pl_add_clipped_text (plDrawLayer2D* ptLayer, plFont* ptFont, float fSize, plVec2 tP, plVec2 tMin, plVec2 tMax, plVec4 tColor, const char* pcText, float fWrap); +plVec2 pl_ui_calculate_text_size (plFont* font, float size, const char* text, float wrap); +static inline float pl_get_frame_height (void) { return gptCtx->tStyle.fFontSize + gptCtx->tStyle.tFramePadding.y * 2.0f; } + +// collision +static inline bool pl_does_circle_contain_point (plVec2 cen, float radius, plVec2 point) { const float fDistanceSquared = powf(point.x - cen.x, 2) + powf(point.y - cen.y, 2); return fDistanceSquared <= radius * radius; } +bool pl_does_triangle_contain_point(plVec2 p0, plVec2 p1, plVec2 p2, plVec2 point); +bool pl_is_item_hoverable (const plRect* ptBox, uint32_t uHash); + +// layouts +static inline plVec2 pl__ui_get_cursor_pos (void) { return (plVec2){gptCtx->ptCurrentWindow->tTempData.tRowPos.x + gptCtx->ptCurrentWindow->tTempData.fAccumRowX + gptCtx->ptCurrentWindow->tTempData.tCurrentLayoutRow.fHorizontalOffset + (float)gptCtx->ptCurrentWindow->tTempData.uTreeDepth * gptCtx->tStyle.fIndentSize, gptCtx->ptCurrentWindow->tTempData.tRowPos.y + gptCtx->ptCurrentWindow->tTempData.tCurrentLayoutRow.fVerticalOffset};} +plVec2 pl_calculate_item_size(float fDefaultHeight); +void pl_advance_cursor (float fWidth, float fHeight); + +// misc +bool pl_begin_window_ex (const char* pcName, bool* pbOpen, plUiWindowFlags tFlags); +void pl_render_scrollbar(plUiWindow* ptWindow, uint32_t uHash, plUiAxis tAxis); +void pl_submit_window (plUiWindow* ptWindow); +void pl__focus_window (plUiWindow* ptWindow); + +static inline bool pl__ui_should_render(const plVec2* ptStartPos, const plVec2* ptWidgetSize) { return !(ptStartPos->y + ptWidgetSize->y < gptCtx->ptCurrentWindow->tPos.y || ptStartPos->y > gptCtx->ptCurrentWindow->tPos.y + gptCtx->ptCurrentWindow->tSize.y); } + +void pl__set_active_id(uint32_t uHash, plUiWindow* ptWindow); + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~text state system~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +static bool pl__input_text_filter_character(unsigned int* puChar, plUiInputTextFlags tFlags); +static inline void pl__text_state_cursor_anim_reset (plUiInputTextState* ptState) { ptState->fCursorAnim = -0.30f; } +static inline void pl__text_state_cursor_clamp (plUiInputTextState* ptState) { ptState->tStb.cursor = pl_min(ptState->tStb.cursor, ptState->iCurrentLengthW); ptState->tStb.select_start = pl_min(ptState->tStb.select_start, ptState->iCurrentLengthW); ptState->tStb.select_end = pl_min(ptState->tStb.select_end, ptState->iCurrentLengthW);} +static inline bool pl__text_state_has_selection (plUiInputTextState* ptState) { return ptState->tStb.select_start != ptState->tStb.select_end; } +static inline void pl__text_state_clear_selection (plUiInputTextState* ptState) { ptState->tStb.select_start = ptState->tStb.select_end = ptState->tStb.cursor; } +static inline int pl__text_state_get_cursor_pos (plUiInputTextState* ptState) { return ptState->tStb.cursor; } +static inline int pl__text_state_get_selection_start(plUiInputTextState* ptState) { return ptState->tStb.select_start; } +static inline int pl__text_state_get_selection_end (plUiInputTextState* ptState) { return ptState->tStb.select_end; } +static inline void pl__text_state_select_all (plUiInputTextState* ptState) { ptState->tStb.select_start = 0; ptState->tStb.cursor = ptState->tStb.select_end = ptState->iCurrentLengthW; ptState->tStb.has_preferred_x = 0; } + +static inline void pl__text_state_clear_text (plUiInputTextState* ptState) { ptState->iCurrentLengthA = ptState->iCurrentLengthW = 0; ptState->sbTextA[0] = 0; ptState->sbTextW[0] = 0; pl__text_state_cursor_clamp(ptState);} +static inline void pl__text_state_free_memory (plUiInputTextState* ptState) { pl_sb_free(ptState->sbTextA); pl_sb_free(ptState->sbTextW); pl_sb_free(ptState->sbInitialTextA);} +static inline int pl__text_state_undo_avail_count(plUiInputTextState* ptState) { return ptState->tStb.undostate.undo_point;} +static inline int pl__text_state_redo_avail_count(plUiInputTextState* ptState) { return STB_TEXTEDIT_UNDOSTATECOUNT - ptState->tStb.undostate.redo_point; } +static void pl__text_state_on_key_press (plUiInputTextState* ptState, int iKey); + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~widget behavior~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +bool pl_button_behavior(const plRect* ptBox, uint32_t uHash, bool* pbOutHovered, bool* pbOutHeld); + +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~storage system~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +plUiStorageEntry* pl_lower_bound(plUiStorageEntry* sbtData, uint32_t uKey); + +int pl_get_int (plUiStorage* ptStorage, uint32_t uKey, int iDefaultValue); +float pl_get_float (plUiStorage* ptStorage, uint32_t uKey, float fDefaultValue); +bool pl_get_bool (plUiStorage* ptStorage, uint32_t uKey, bool bDefaultValue); +void* pl_get_ptr (plUiStorage* ptStorage, uint32_t uKey); + +int* pl_get_int_ptr (plUiStorage* ptStorage, uint32_t uKey, int iDefaultValue); +float* pl_get_float_ptr(plUiStorage* ptStorage, uint32_t uKey, float fDefaultValue); +bool* pl_get_bool_ptr (plUiStorage* ptStorage, uint32_t uKey, bool bDefaultValue); +void** pl_get_ptr_ptr (plUiStorage* ptStorage, uint32_t uKey, void* pDefaultValue); + +void pl_set_int (plUiStorage* ptStorage, uint32_t uKey, int iValue); +void pl_set_float (plUiStorage* ptStorage, uint32_t uKey, float fValue); +void pl_set_bool (plUiStorage* ptStorage, uint32_t uKey, bool bValue); +void pl_set_ptr (plUiStorage* ptStorage, uint32_t uKey, void* pValue); + +#endif // PL_UI_INTERNAL_H \ No newline at end of file diff --git a/ui/pl_ui_widgets.c b/ui/pl_ui_widgets.c new file mode 100644 index 00000000..5ad431b2 --- /dev/null +++ b/ui/pl_ui_widgets.c @@ -0,0 +1,2375 @@ +#include "pl_ui_internal.h" +#include // FLT_MAX +#define PL_MATH_INCLUDE_FUNCTIONS +#include "pl_math.h" + +//----------------------------------------------------------------------------- +// [SECTION] stb_text mess +//----------------------------------------------------------------------------- + +static plVec2 +pl__input_text_calc_text_size_w(const plUiWChar* text_begin, const plUiWChar* text_end, const plUiWChar** remaining, plVec2* out_offset, bool stop_on_new_line) +{ + plFont* font = gptCtx->ptFont; + const float line_height = gptCtx->tStyle.fFontSize; + const float scale = line_height / font->tConfig.fFontSize; + + plVec2 text_size = {0}; + float line_width = 0.0f; + + const plUiWChar* s = text_begin; + while (s < text_end) + { + unsigned int c = (unsigned int)(*s++); + if (c == '\n') + { + text_size.x = pl_max(text_size.x, line_width); + text_size.y += line_height; + line_width = 0.0f; + if (stop_on_new_line) + break; + continue; + } + if (c == '\r') + continue; + + const float char_width = font->sbtGlyphs[font->sbuCodePoints[(plUiWChar)c]].xAdvance * scale; + line_width += char_width; + } + + if (text_size.x < line_width) + text_size.x = line_width; + + if (out_offset) + *out_offset = (plVec2){line_width, text_size.y + line_height}; // offset allow for the possibility of sitting after a trailing \n + + if (line_width > 0 || text_size.y == 0.0f) // whereas size.y will ignore the trailing \n + text_size.y += line_height; + + if (remaining) + *remaining = s; + + return text_size; +} + + +static int STB_TEXTEDIT_STRINGLEN(const plUiInputTextState* obj) { return obj->iCurrentLengthW; } +static plUiWChar STB_TEXTEDIT_GETCHAR(const plUiInputTextState* obj, int idx) { return obj->sbTextW[idx]; } +static float STB_TEXTEDIT_GETWIDTH(plUiInputTextState* obj, int line_start_idx, int char_idx) { plUiWChar c = obj->sbTextW[line_start_idx + char_idx]; if (c == '\n') return STB_TEXTEDIT_GETWIDTH_NEWLINE; return gptCtx->ptFont->sbtGlyphs[gptCtx->ptFont->sbuCodePoints[c]].xAdvance * (gptCtx->tStyle.fFontSize / gptCtx->ptFont->tConfig.fFontSize); } +static int STB_TEXTEDIT_KEYTOTEXT(int key) { return key >= 0x200000 ? 0 : key; } +static plUiWChar STB_TEXTEDIT_NEWLINE = '\n'; +static void STB_TEXTEDIT_LAYOUTROW(StbTexteditRow* r, plUiInputTextState* obj, int line_start_idx) +{ + const plUiWChar* text = obj->sbTextW; + const plUiWChar* text_remaining = NULL; + const plVec2 size = pl__input_text_calc_text_size_w(text + line_start_idx, text + obj->iCurrentLengthW, &text_remaining, NULL, true); + r->x0 = 0.0f; + r->x1 = size.x; + r->baseline_y_delta = size.y; + r->ymin = 0.0f; + r->ymax = size.y; + r->num_chars = (int)(text_remaining - (text + line_start_idx)); +} + +static bool pl__is_separator(unsigned int c) +{ + return c==',' || c==';' || c=='(' || c==')' || c=='{' || c=='}' || c=='[' || c==']' || c=='|' || c=='\n' || c=='\r' || c=='.' || c=='!'; +} +static inline bool pl__char_is_blank_w(unsigned int c) { return c == ' ' || c == '\t' || c == 0x3000; } + +static int pl__is_word_boundary_from_right(plUiInputTextState* obj, int idx) +{ + // When PL_UI_INPUT_TEXT_FLAGS_PASSWORD is set, we don't want actions such as CTRL+Arrow to leak the fact that underlying data are blanks or separators. + // if ((obj->tFlags & PL_UI_INPUT_TEXT_FLAGS_PASSWORD) || idx <= 0) + // return 0; + + bool prev_white = pl__char_is_blank_w(obj->sbTextW[idx - 1]); + bool prev_separ = pl__is_separator(obj->sbTextW[idx - 1]); + bool curr_white = pl__char_is_blank_w(obj->sbTextW[idx]); + bool curr_separ = pl__is_separator(obj->sbTextW[idx]); + return ((prev_white || prev_separ) && !(curr_separ || curr_white)) || (curr_separ && !prev_separ); +} +static int pl__is_word_boundary_from_left(plUiInputTextState* obj, int idx) +{ + // if ((obj->Flags & ImGuiInputTextFlags_Password) || idx <= 0) + // return 0; + + bool prev_white = pl__char_is_blank_w(obj->sbTextW[idx]); + bool prev_separ = pl__is_separator(obj->sbTextW[idx]); + bool curr_white = pl__char_is_blank_w(obj->sbTextW[idx - 1]); + bool curr_separ = pl__is_separator(obj->sbTextW[idx - 1]); + return ((prev_white) && !(curr_separ || curr_white)) || (curr_separ && !prev_separ); +} +static int STB_TEXTEDIT_MOVEWORDLEFT_IMPL(plUiInputTextState* obj, int idx) { idx--; while (idx >= 0 && !pl__is_word_boundary_from_right(obj, idx)) idx--; return idx < 0 ? 0 : idx; } +static int STB_TEXTEDIT_MOVEWORDRIGHT_MAC(plUiInputTextState* obj, int idx) { idx++; int len = obj->iCurrentLengthW; while (idx < len && !pl__is_word_boundary_from_left(obj, idx)) idx++; return idx > len ? len : idx; } +static int STB_TEXTEDIT_MOVEWORDRIGHT_WIN(plUiInputTextState* obj, int idx) { idx++; int len = obj->iCurrentLengthW; while (idx < len && !pl__is_word_boundary_from_right(obj, idx)) idx++; return idx > len ? len : idx; } +// static int STB_TEXTEDIT_MOVEWORDRIGHT_IMPL(plUiInputTextState* obj, int idx) { if (g.IO.ConfigMacOSXBehaviors) return STB_TEXTEDIT_MOVEWORDRIGHT_MAC(obj, idx); else return STB_TEXTEDIT_MOVEWORDRIGHT_WIN(obj, idx); } +static int STB_TEXTEDIT_MOVEWORDRIGHT_IMPL(plUiInputTextState* obj, int idx) { return STB_TEXTEDIT_MOVEWORDRIGHT_WIN(obj, idx); } +#define STB_TEXTEDIT_MOVEWORDLEFT STB_TEXTEDIT_MOVEWORDLEFT_IMPL // They need to be #define for stb_textedit.h +#define STB_TEXTEDIT_MOVEWORDRIGHT STB_TEXTEDIT_MOVEWORDRIGHT_IMPL + +static inline int pl__text_count_utf8_bytes_from_char(unsigned int c) +{ + if (c < 0x80) return 1; + if (c < 0x800) return 2; + if (c < 0x10000) return 3; + if (c <= 0x10FFFF) return 4; + return 3; +} + +static int pl__text_count_utf8_bytes_from_str(const plUiWChar* in_text, const plUiWChar* in_text_end) +{ + int bytes_count = 0; + while ((!in_text_end || in_text < in_text_end) && *in_text) + { + unsigned int c = (unsigned int)(*in_text++); + if (c < 0x80) + bytes_count++; + else + bytes_count += pl__text_count_utf8_bytes_from_char(c); + } + return bytes_count; +} + +static void STB_TEXTEDIT_DELETECHARS(plUiInputTextState* obj, int pos, int n) +{ + plUiWChar* dst = obj->sbTextW + pos; + + // We maintain our buffer length in both UTF-8 and wchar formats + obj->bEdited = true; + obj->iCurrentLengthA -= pl__text_count_utf8_bytes_from_str(dst, dst + n); + obj->iCurrentLengthW -= n; + + // Offset remaining text (FIXME-OPT: Use memmove) + const plUiWChar* src = obj->sbTextW + pos + n; + plUiWChar c = *src++; + while (c) + { + *dst++ = c; + c = *src++; + } + *dst = '\0'; +} + +static bool STB_TEXTEDIT_INSERTCHARS(plUiInputTextState* obj, int pos, const plUiWChar* new_text, int new_text_len) +{ + // const bool is_resizable = (obj->Flags & ImGuiInputTextFlags_CallbackResize) != 0; + const bool is_resizable = false; + const int text_len = obj->iCurrentLengthW; + PL_ASSERT(pos <= text_len); + + const int new_text_len_utf8 = pl__text_count_utf8_bytes_from_str(new_text, new_text + new_text_len); + if (!is_resizable && (new_text_len_utf8 + obj->iCurrentLengthA + 1 > obj->iBufferCapacityA)) + return false; + + // Grow internal buffer if needed + if (new_text_len + text_len + 1 > (int)pl_sb_size(obj->sbTextW)) + { + if (!is_resizable) + return false; + PL_ASSERT(text_len < (int)pl_sb_size(obj->sbTextW)); + pl_sb_resize(obj->sbTextW, text_len + pl_clampi(32, new_text_len * 4, pl_max(256, new_text_len)) + 1); + } + + plUiWChar* text = obj->sbTextW; + if (pos != text_len) + memmove(text + pos + new_text_len, text + pos, (size_t)(text_len - pos) * sizeof(plUiWChar)); + memcpy(text + pos, new_text, (size_t)new_text_len * sizeof(plUiWChar)); + + obj->bEdited = true; + obj->iCurrentLengthW += new_text_len; + obj->iCurrentLengthA += new_text_len_utf8; + obj->sbTextW[obj->iCurrentLengthW] = '\0'; + + return true; +} + +// We don't use an enum so we can build even with conflicting symbols (if another user of stb_textedit.h leak their STB_TEXTEDIT_K_* symbols) +#define STB_TEXTEDIT_K_LEFT 0x200000 // keyboard input to move cursor left +#define STB_TEXTEDIT_K_RIGHT 0x200001 // keyboard input to move cursor right +#define STB_TEXTEDIT_K_UP 0x200002 // keyboard input to move cursor up +#define STB_TEXTEDIT_K_DOWN 0x200003 // keyboard input to move cursor down +#define STB_TEXTEDIT_K_LINESTART 0x200004 // keyboard input to move cursor to start of line +#define STB_TEXTEDIT_K_LINEEND 0x200005 // keyboard input to move cursor to end of line +#define STB_TEXTEDIT_K_TEXTSTART 0x200006 // keyboard input to move cursor to start of text +#define STB_TEXTEDIT_K_TEXTEND 0x200007 // keyboard input to move cursor to end of text +#define STB_TEXTEDIT_K_DELETE 0x200008 // keyboard input to delete selection or character under cursor +#define STB_TEXTEDIT_K_BACKSPACE 0x200009 // keyboard input to delete selection or character left of cursor +#define STB_TEXTEDIT_K_UNDO 0x20000A // keyboard input to perform undo +#define STB_TEXTEDIT_K_REDO 0x20000B // keyboard input to perform redo +#define STB_TEXTEDIT_K_WORDLEFT 0x20000C // keyboard input to move cursor left one word +#define STB_TEXTEDIT_K_WORDRIGHT 0x20000D // keyboard input to move cursor right one word +#define STB_TEXTEDIT_K_PGUP 0x20000E // keyboard input to move cursor up a page +#define STB_TEXTEDIT_K_PGDOWN 0x20000F // keyboard input to move cursor down a page +#define STB_TEXTEDIT_K_SHIFT 0x400000 + +#define STB_TEXTEDIT_IMPLEMENTATION +#include "stb_textedit.h" + +// stb_textedit internally allows for a single undo record to do addition and deletion, but somehow, calling +// the stb_textedit_paste() function creates two separate records, so we perform it manually. (FIXME: Report to nothings/stb?) +static void stb_textedit_replace(plUiInputTextState* str, STB_TexteditState* state, const STB_TEXTEDIT_CHARTYPE* text, int text_len) +{ + stb_text_makeundo_replace(str, state, 0, str->iCurrentLengthW, text_len); + STB_TEXTEDIT_DELETECHARS(str, 0, str->iCurrentLengthW); + state->cursor = state->select_start = state->select_end = 0; + if (text_len <= 0) + return; + if (STB_TEXTEDIT_INSERTCHARS(str, 0, text, text_len)) + { + state->cursor = state->select_start = state->select_end = text_len; + state->has_preferred_x = 0; + return; + } + PL_ASSERT(0); // Failed to insert character, normally shouldn't happen because of how we currently use stb_textedit_replace() +} + +static void +pl__text_state_on_key_press(plUiInputTextState* ptState, int iKey) +{ + stb_textedit_key(ptState, &ptState->tStb, iKey); + ptState->bCursorFollow = true; + pl__text_state_cursor_anim_reset(ptState); +} + +// Based on stb_to_utf8() from github.com/nothings/stb/ +static inline int +pl__text_char_to_utf8_inline(char* buf, int buf_size, unsigned int c) +{ + if (c < 0x80) + { + buf[0] = (char)c; + return 1; + } + if (c < 0x800) + { + if (buf_size < 2) return 0; + buf[0] = (char)(0xc0 + (c >> 6)); + buf[1] = (char)(0x80 + (c & 0x3f)); + return 2; + } + if (c < 0x10000) + { + if (buf_size < 3) return 0; + buf[0] = (char)(0xe0 + (c >> 12)); + buf[1] = (char)(0x80 + ((c >> 6) & 0x3f)); + buf[2] = (char)(0x80 + ((c ) & 0x3f)); + return 3; + } + if (c <= 0x10FFFF) + { + if (buf_size < 4) return 0; + buf[0] = (char)(0xf0 + (c >> 18)); + buf[1] = (char)(0x80 + ((c >> 12) & 0x3f)); + buf[2] = (char)(0x80 + ((c >> 6) & 0x3f)); + buf[3] = (char)(0x80 + ((c ) & 0x3f)); + return 4; + } + // Invalid code point, the max unicode is 0x10FFFF + return 0; +} + +static inline plUiInputTextState* +pl__get_input_text_state(uint32_t id) +{ + return (id != 0 && gptCtx->tInputTextState.uId == id) ? &gptCtx->tInputTextState : NULL; +} + +static const plUiWChar* +pl__str_bol_w(const plUiWChar* buf_mid_line, const plUiWChar* buf_begin) // find beginning-of-line +{ + while (buf_mid_line > buf_begin && buf_mid_line[-1] != '\n') + buf_mid_line--; + return buf_mid_line; +} + +static int +pl__text_str_to_utf8(char* out_buf, int out_buf_size, const plUiWChar* in_text, const plUiWChar* in_text_end) +{ + char* buf_p = out_buf; + const char* buf_end = out_buf + out_buf_size; + while (buf_p < buf_end - 1 && (!in_text_end || in_text < in_text_end) && *in_text) + { + unsigned int c = (unsigned int)(*in_text++); + if (c < 0x80) + *buf_p++ = (char)c; + else + buf_p += pl__text_char_to_utf8_inline(buf_p, (int)(buf_end - buf_p - 1), c); + } + *buf_p = 0; + return (int)(buf_p - out_buf); +} + +static int +pl__text_str_from_utf8(plUiWChar* buf, int buf_size, const char* in_text, const char* in_text_end, const char** in_text_remaining) +{ + plUiWChar* buf_out = buf; + plUiWChar* buf_end = buf + buf_size; + while (buf_out < buf_end - 1 && (!in_text_end || in_text < in_text_end) && *in_text) + { + unsigned int c; + in_text += pl_text_char_from_utf8(&c, in_text, in_text_end); + *buf_out++ = (plUiWChar)c; + } + *buf_out = 0; + if (in_text_remaining) + *in_text_remaining = in_text; + return (int)(buf_out - buf); +} + +static int +pl__text_count_chars_from_utf8(const char* in_text, const char* in_text_end) +{ + int char_count = 0; + while ((!in_text_end || in_text < in_text_end) && *in_text) + { + unsigned int c; + in_text += pl_text_char_from_utf8(&c, in_text, in_text_end); + char_count++; + } + return char_count; +} + +static int +pl__input_text_calc_text_len_and_line_count(const char* text_begin, const char** out_text_end) +{ + int line_count = 0; + const char* s = text_begin; + char c = *s++; + while (c) // We are only matching for \n so we can ignore UTF-8 decoding + { + if (c == '\n') + line_count++; + c = *s++; + } + s--; + if (s[0] != '\n' && s[0] != '\r') + line_count++; + *out_text_end = s; + return line_count; +} + +bool +pl_button_behavior(const plRect* ptBox, uint32_t uHash, bool* pbOutHovered, bool* pbOutHeld) +{ + plUiWindow* ptWindow = gptCtx->ptCurrentWindow; + gptCtx->tPrevItemData.bActive = false; + + bool bPressed = false; + bool bHovered = pl_is_item_hoverable(ptBox, uHash); + + if(bHovered) + gptCtx->uNextHoveredId = uHash; + + bool bHeld = bHovered && gptIOI->is_mouse_down(PL_MOUSE_BUTTON_LEFT); + + if(uHash == gptCtx->uActiveId) + { + gptCtx->tPrevItemData.bActive = true; + + if(bHeld) + pl__set_active_id(uHash, ptWindow); + } + + if(bHovered) + { + if(gptIOI->is_mouse_clicked(PL_MOUSE_BUTTON_LEFT, false)) + { + pl__set_active_id(uHash, ptWindow); + gptCtx->tPrevItemData.bActive = true; + } + else if(gptIOI->is_mouse_released(PL_MOUSE_BUTTON_LEFT)) + { + bPressed = uHash == gptCtx->uActiveId; + pl__set_active_id(0, ptWindow); + } + } + + *pbOutHeld = bHeld; + *pbOutHovered = bHovered; + gptCtx->tPrevItemData.bHovered = bHovered; + return bPressed; +} + +bool +pl_button(const char* pcText) +{ + plUiWindow* ptWindow = gptCtx->ptCurrentWindow; + plUiLayoutRow* ptCurrentRow = &ptWindow->tTempData.tCurrentLayoutRow; + const plVec2 tWidgetSize = pl_calculate_item_size(pl_get_frame_height()); + const plVec2 tStartPos = pl__ui_get_cursor_pos(); + bool bPressed = false; + if(pl__ui_should_render(&tStartPos, &tWidgetSize)) + { + const uint32_t uHash = pl_str_hash(pcText, 0, pl_sb_top(gptCtx->sbuIdStack)); + plRect tBoundingBox = pl_calculate_rect(tStartPos, tWidgetSize); + const plRect* ptClipRect = gptDraw->get_clip_rect(gptCtx->ptDrawlist); + tBoundingBox = pl_rect_clip_full(&tBoundingBox, ptClipRect); + + bool bHovered = false; + bool bHeld = false; + bPressed = pl_button_behavior(&tBoundingBox, uHash, &bHovered, &bHeld); + + if(gptCtx->uActiveId == uHash) gptDraw->add_rect_filled(ptWindow->ptFgLayer, tStartPos, tBoundingBox.tMax, gptCtx->tColorScheme.tButtonActiveCol); + else if(gptCtx->uHoveredId == uHash) gptDraw->add_rect_filled(ptWindow->ptFgLayer, tStartPos, tBoundingBox.tMax, gptCtx->tColorScheme.tButtonHoveredCol); + else gptDraw->add_rect_filled(ptWindow->ptFgLayer, tStartPos, tBoundingBox.tMax, gptCtx->tColorScheme.tButtonCol); + + const plVec2 tTextSize = pl_ui_calculate_text_size(gptCtx->ptFont, gptCtx->tStyle.fFontSize, pcText, -1.0f); + const plRect tTextBounding = gptDraw->calculate_text_bb_ex(gptCtx->ptFont, gptCtx->tStyle.fFontSize, tStartPos, pcText, pl_find_renderered_text_end(pcText, NULL), -1.0f); + const plVec2 tTextActualCenter = pl_rect_center(&tTextBounding); + + plVec2 tTextStartPos = { + .x = tStartPos.x, + .y = tStartPos.y + tStartPos.y + tWidgetSize.y / 2.0f - tTextActualCenter.y + }; + + if((pl_rect_width(&tBoundingBox) < tTextSize.x)) // clipping, so start at beginning of widget + tTextStartPos.x += gptCtx->tStyle.tFramePadding.x; + else // not clipping, so center on widget + tTextStartPos.x += tStartPos.x + tWidgetSize.x / 2.0f - tTextActualCenter.x; + + pl_add_clipped_text(ptWindow->ptFgLayer, gptCtx->ptFont, gptCtx->tStyle.fFontSize, tTextStartPos, tBoundingBox.tMin, tBoundingBox.tMax, gptCtx->tColorScheme.tTextCol, pcText, 0.0f); + } + pl_advance_cursor(tWidgetSize.x, tWidgetSize.y); + return bPressed; +} + +bool +pl_selectable(const char* pcText, bool* bpValue) +{ + + // temporary hack + static bool bDummyState = true; + if(bpValue == NULL) bpValue = &bDummyState; + + plUiWindow* ptWindow = gptCtx->ptCurrentWindow; + plUiLayoutRow* ptCurrentRow = &ptWindow->tTempData.tCurrentLayoutRow; + const plVec2 tWidgetSize = pl_calculate_item_size(pl_get_frame_height()); + const plVec2 tStartPos = pl__ui_get_cursor_pos(); + + bool bPressed = false; + if(pl__ui_should_render(&tStartPos, &tWidgetSize)) + { + const uint32_t uHash = pl_str_hash(pcText, 0, pl_sb_top(gptCtx->sbuIdStack)); + + plRect tTextBounding = gptDraw->calculate_text_bb_ex(gptCtx->ptFont, gptCtx->tStyle.fFontSize, tStartPos, pcText, pl_find_renderered_text_end(pcText, NULL), -1.0f); + const plVec2 tTextActualCenter = pl_rect_center(&tTextBounding); + + const plVec2 tTextStartPos = { + .x = tStartPos.x + gptCtx->tStyle.tFramePadding.x, + .y = tStartPos.y + tStartPos.y + tWidgetSize.y / 2.0f - tTextActualCenter.y + }; + + const plVec2 tEndPos = pl_add_vec2(tStartPos, tWidgetSize); + + plRect tBoundingBox = pl_calculate_rect(tStartPos, tWidgetSize); + const plRect* ptClipRect = gptDraw->get_clip_rect(gptCtx->ptDrawlist); + tBoundingBox = pl_rect_clip_full(&tBoundingBox, ptClipRect); + bool bHovered = false; + bool bHeld = false; + bPressed = pl_button_behavior(&tBoundingBox, uHash, &bHovered, &bHeld); + + if(bPressed) + *bpValue = !*bpValue; + + if(gptCtx->uActiveId == uHash) gptDraw->add_rect_filled(ptWindow->ptFgLayer, tStartPos, tEndPos, gptCtx->tColorScheme.tHeaderActiveCol); + else if(gptCtx->uHoveredId == uHash) gptDraw->add_rect_filled(ptWindow->ptFgLayer, tStartPos, tEndPos, gptCtx->tColorScheme.tHeaderHoveredCol); + + if(*bpValue) + gptDraw->add_rect_filled(ptWindow->ptFgLayer, tStartPos, tEndPos, gptCtx->tColorScheme.tHeaderCol); + + pl_ui_add_text(ptWindow->ptFgLayer, gptCtx->ptFont, gptCtx->tStyle.fFontSize, tTextStartPos, gptCtx->tColorScheme.tTextCol, pcText, -1.0f); + } + pl_advance_cursor(tWidgetSize.x, tWidgetSize.y); + return bPressed; +} + +bool +pl_checkbox(const char* pcText, bool* bpValue) +{ + // temporary hack + static bool bDummyState = true; + if(bpValue == NULL) bpValue = &bDummyState; + + plUiWindow* ptWindow = gptCtx->ptCurrentWindow; + plUiLayoutRow* ptCurrentRow = &ptWindow->tTempData.tCurrentLayoutRow; + const plVec2 tWidgetSize = pl_calculate_item_size(pl_get_frame_height()); + const plVec2 tStartPos = pl__ui_get_cursor_pos(); + + bool bPressed = false; + if(pl__ui_should_render(&tStartPos, &tWidgetSize)) + { + const bool bOriginalValue = *bpValue; + const uint32_t uHash = pl_str_hash(pcText, 0, pl_sb_top(gptCtx->sbuIdStack)); + plRect tTextBounding = gptDraw->calculate_text_bb_ex(gptCtx->ptFont, gptCtx->tStyle.fFontSize, tStartPos, pcText, pl_find_renderered_text_end(pcText, NULL), -1.0f); + const plVec2 tTextActualCenter = pl_rect_center(&tTextBounding); + + + const plVec2 tTextStartPos = { + .x = tStartPos.x + tWidgetSize.y + gptCtx->tStyle.tInnerSpacing.x, + .y = tStartPos.y + tStartPos.y + tWidgetSize.y / 2.0f - tTextActualCenter.y + }; + + plRect tBoundingBox = pl_calculate_rect(tStartPos, (plVec2){tWidgetSize.y, tWidgetSize.y}); + const plRect* ptClipRect = gptDraw->get_clip_rect(gptCtx->ptDrawlist); + tBoundingBox = pl_rect_clip_full(&tBoundingBox, ptClipRect); + bool bHovered = false; + bool bHeld = false; + bPressed = pl_button_behavior(&tBoundingBox, uHash, &bHovered, &bHeld); + + if(bPressed) + *bpValue = !bOriginalValue; + + if(gptCtx->uActiveId == uHash) gptDraw->add_rect_filled(ptWindow->ptFgLayer, tStartPos, tBoundingBox.tMax, gptCtx->tColorScheme.tFrameBgActiveCol); + else if(gptCtx->uHoveredId == uHash) gptDraw->add_rect_filled(ptWindow->ptFgLayer, tStartPos, tBoundingBox.tMax, gptCtx->tColorScheme.tFrameBgHoveredCol); + else gptDraw->add_rect_filled(ptWindow->ptFgLayer, tStartPos, tBoundingBox.tMax, gptCtx->tColorScheme.tFrameBgCol); + + if(*bpValue) + gptDraw->add_line(ptWindow->ptFgLayer, tStartPos, tBoundingBox.tMax, gptCtx->tColorScheme.tCheckmarkCol, 2.0f); + + // add label + pl_ui_add_text(ptWindow->ptFgLayer, gptCtx->ptFont, gptCtx->tStyle.fFontSize, tTextStartPos, gptCtx->tColorScheme.tTextCol, pcText, -1.0f); + } + pl_advance_cursor(tWidgetSize.x, tWidgetSize.y); + return bPressed; +} + +bool +pl_radio_button(const char* pcText, int* piValue, int iButtonValue) +{ + plUiWindow* ptWindow = gptCtx->ptCurrentWindow; + plUiLayoutRow* ptCurrentRow = &ptWindow->tTempData.tCurrentLayoutRow; + const plVec2 tWidgetSize = pl_calculate_item_size(pl_get_frame_height()); + const plVec2 tStartPos = pl__ui_get_cursor_pos(); + + bool bPressed = false; + if(pl__ui_should_render(&tStartPos, &tWidgetSize)) + { + const uint32_t uHash = pl_str_hash(pcText, 0, pl_sb_top(gptCtx->sbuIdStack)); + const plVec2 tTextSize = pl_ui_calculate_text_size(gptCtx->ptFont, gptCtx->tStyle.fFontSize, pcText, -1.0f); + plRect tTextBounding = gptDraw->calculate_text_bb_ex(gptCtx->ptFont, gptCtx->tStyle.fFontSize, tStartPos, pcText, pl_find_renderered_text_end(pcText, NULL), -1.0f); + const plVec2 tTextActualCenter = pl_rect_center(&tTextBounding); + + const plVec2 tSize = {tTextSize.x + 2.0f * gptCtx->tStyle.tFramePadding.x + gptCtx->tStyle.tInnerSpacing.x + tWidgetSize.y, tWidgetSize.y}; + // tSize = pl_floor_vec2(tSize); + + const plVec2 tTextStartPos = { + .x = tStartPos.x + tWidgetSize.y + gptCtx->tStyle.tInnerSpacing.x + gptCtx->tStyle.tFramePadding.x, + .y = tStartPos.y + tStartPos.y + tWidgetSize.y / 2.0f - tTextActualCenter.y + }; + + plRect tBoundingBox = pl_rect_expand_vec2(&tTextBounding, (plVec2){0.5f * (gptCtx->tStyle.tFramePadding.x + gptCtx->tStyle.tInnerSpacing.x + tWidgetSize.y), 0.0f}); + tBoundingBox = pl_rect_move_start_x(&tBoundingBox, tStartPos.x + gptCtx->tStyle.tFramePadding.x); + const plRect* ptClipRect = gptDraw->get_clip_rect(gptCtx->ptDrawlist); + tBoundingBox = pl_rect_clip_full(&tBoundingBox, ptClipRect); + bool bHovered = false; + bool bHeld = false; + bPressed = pl_button_behavior(&tBoundingBox, uHash, &bHovered, &bHeld); + + if(bPressed) + *piValue = iButtonValue; + + if(gptCtx->uActiveId == uHash) gptDraw->add_circle_filled(ptWindow->ptFgLayer, (plVec2){tStartPos.x + tWidgetSize.y / 2.0f, tStartPos.y + tWidgetSize.y / 2.0f}, gptCtx->tStyle.fFontSize / 1.5f, gptCtx->tColorScheme.tFrameBgActiveCol, 12); + else if(gptCtx->uHoveredId == uHash) gptDraw->add_circle_filled(ptWindow->ptFgLayer, (plVec2){tStartPos.x + tWidgetSize.y / 2.0f, tStartPos.y + tWidgetSize.y / 2.0f}, gptCtx->tStyle.fFontSize / 1.5f, gptCtx->tColorScheme.tFrameBgHoveredCol, 12); + else gptDraw->add_circle_filled(ptWindow->ptFgLayer, (plVec2){tStartPos.x + tWidgetSize.y / 2.0f, tStartPos.y + tWidgetSize.y / 2.0f}, gptCtx->tStyle.fFontSize / 1.5f, gptCtx->tColorScheme.tFrameBgCol, 12); + + if(*piValue == iButtonValue) + gptDraw->add_circle_filled(ptWindow->ptFgLayer, (plVec2){tStartPos.x + tWidgetSize.y / 2.0f, tStartPos.y + tWidgetSize.y / 2.0f}, gptCtx->tStyle.fFontSize / 2.5f, gptCtx->tColorScheme.tCheckmarkCol, 12); + + pl_ui_add_text(ptWindow->ptFgLayer, gptCtx->ptFont, gptCtx->tStyle.fFontSize, tTextStartPos, gptCtx->tColorScheme.tTextCol, pcText, -1.0f); + } + pl_advance_cursor(tWidgetSize.x, tWidgetSize.y); + return bPressed; +} + +bool +pl_collapsing_header(const char* pcText) +{ + plUiWindow* ptWindow = gptCtx->ptCurrentWindow; + plUiLayoutRow* ptCurrentRow = &ptWindow->tTempData.tCurrentLayoutRow; + const plVec2 tWidgetSize = pl_calculate_item_size(pl_get_frame_height()); + const plVec2 tStartPos = pl__ui_get_cursor_pos(); + const uint32_t uHash = pl_str_hash(pcText, 0, pl_sb_top(gptCtx->sbuIdStack)); + bool* pbOpenState = pl_get_bool_ptr(&ptWindow->tStorage, uHash, false); + if(pl__ui_should_render(&tStartPos, &tWidgetSize)) + { + plRect tTextBounding = gptDraw->calculate_text_bb_ex(gptCtx->ptFont, gptCtx->tStyle.fFontSize, tStartPos, pcText, pl_find_renderered_text_end(pcText, NULL), -1.0f); + const plVec2 tTextActualCenter = pl_rect_center(&tTextBounding); + + const plVec2 tTextStartPos = { + .x = tStartPos.x + tWidgetSize.y * 1.5f, + .y = tStartPos.y + tStartPos.y + tWidgetSize.y / 2.0f - tTextActualCenter.y + }; + + plRect tBoundingBox = pl_calculate_rect(tStartPos, tWidgetSize); + const plRect* ptClipRect = gptDraw->get_clip_rect(gptCtx->ptDrawlist); + tBoundingBox = pl_rect_clip_full(&tBoundingBox, ptClipRect); + bool bHovered = false; + bool bHeld = false; + const bool bPressed = pl_button_behavior(&tBoundingBox, uHash, &bHovered, &bHeld); + + if(bPressed) + *pbOpenState = !*pbOpenState; + + if(gptCtx->uActiveId == uHash) gptDraw->add_rect_filled(ptWindow->ptFgLayer, tStartPos, tBoundingBox.tMax, gptCtx->tColorScheme.tHeaderActiveCol); + else if(gptCtx->uHoveredId == uHash) gptDraw->add_rect_filled(ptWindow->ptFgLayer, tStartPos, tBoundingBox.tMax, gptCtx->tColorScheme.tHeaderHoveredCol); + else gptDraw->add_rect_filled(ptWindow->ptFgLayer, tStartPos, tBoundingBox.tMax, gptCtx->tColorScheme.tHeaderCol); + + if(*pbOpenState) + { + const plVec2 centerPoint = {tStartPos.x + 8.0f * 1.5f, tStartPos.y + tWidgetSize.y / 2.0f}; + const plVec2 pointPos = pl_add_vec2(centerPoint, (plVec2){ 0.0f, 4.0f}); + const plVec2 rightPos = pl_add_vec2(centerPoint, (plVec2){ 4.0f, -4.0f}); + const plVec2 leftPos = pl_add_vec2(centerPoint, (plVec2){-4.0f, -4.0f}); + gptDraw->add_triangle_filled(ptWindow->ptFgLayer, pointPos, rightPos, leftPos, (plVec4){1.0f, 1.0f, 1.0f, 1.0f}); + } + else + { + const plVec2 centerPoint = {tStartPos.x + 8.0f * 1.5f, tStartPos.y + tWidgetSize.y / 2.0f}; + const plVec2 pointPos = pl_add_vec2(centerPoint, (plVec2){ 4.0f, 0.0f}); + const plVec2 rightPos = pl_add_vec2(centerPoint, (plVec2){ -4.0f, -4.0f}); + const plVec2 leftPos = pl_add_vec2(centerPoint, (plVec2){ -4.0f, 4.0f}); + gptDraw->add_triangle_filled(ptWindow->ptFgLayer, pointPos, rightPos, leftPos, (plVec4){1.0f, 1.0f, 1.0f, 1.0f}); + } + + pl_ui_add_text(ptWindow->ptFgLayer, gptCtx->ptFont, gptCtx->tStyle.fFontSize, tTextStartPos, gptCtx->tColorScheme.tTextCol, pcText, -1.0f); + } + if(*pbOpenState) + pl_sb_push(ptWindow->sbtRowStack, ptWindow->tTempData.tCurrentLayoutRow); + pl_advance_cursor(tWidgetSize.x, tWidgetSize.y); + return *pbOpenState; +} + +void +pl_end_collapsing_header(void) +{ + plUiWindow* ptWindow = gptCtx->ptCurrentWindow; + ptWindow->tTempData.tCurrentLayoutRow = pl_sb_pop(ptWindow->sbtRowStack); +} + +bool +pl_tree_node(const char* pcText) +{ + plUiWindow* ptWindow = gptCtx->ptCurrentWindow; + pl_sb_push(ptWindow->sbtRowStack, ptWindow->tTempData.tCurrentLayoutRow); + plUiLayoutRow* ptCurrentRow = &ptWindow->tTempData.tCurrentLayoutRow; + const plVec2 tWidgetSize = pl_calculate_item_size(pl_get_frame_height()); + const plVec2 tStartPos = pl__ui_get_cursor_pos(); + + const uint32_t uHash = pl_str_hash(pcText, 0, pl_sb_top(gptCtx->sbuIdStack)); + + bool* pbOpenState = pl_get_bool_ptr(&ptWindow->tStorage, uHash, false); + if(pl__ui_should_render(&tStartPos, &tWidgetSize)) + { + + plRect tTextBounding = gptDraw->calculate_text_bb_ex(gptCtx->ptFont, gptCtx->tStyle.fFontSize, tStartPos, pcText, pl_find_renderered_text_end(pcText, NULL), -1.0f); + const plVec2 tTextActualCenter = pl_rect_center(&tTextBounding); + + plRect tBoundingBox = pl_calculate_rect(tStartPos, tWidgetSize); + const plRect* ptClipRect = gptDraw->get_clip_rect(gptCtx->ptDrawlist); + tBoundingBox = pl_rect_clip_full(&tBoundingBox, ptClipRect); + bool bHovered = false; + bool bHeld = false; + const bool bPressed = pl_button_behavior(&tBoundingBox, uHash, &bHovered, &bHeld); + + if(bPressed) + *pbOpenState = !*pbOpenState; + + if(gptCtx->uActiveId == uHash) gptDraw->add_rect_filled(ptWindow->ptFgLayer, tStartPos, tBoundingBox.tMax, gptCtx->tColorScheme.tHeaderActiveCol); + else if(gptCtx->uHoveredId == uHash) gptDraw->add_rect_filled(ptWindow->ptFgLayer, tStartPos, tBoundingBox.tMax, gptCtx->tColorScheme.tHeaderHoveredCol); + + if(*pbOpenState) + { + const plVec2 centerPoint = {tStartPos.x + 8.0f * 1.5f, tStartPos.y + tWidgetSize.y / 2.0f}; + const plVec2 pointPos = pl_add_vec2(centerPoint, (plVec2){ 0.0f, 4.0f}); + const plVec2 rightPos = pl_add_vec2(centerPoint, (plVec2){ 4.0f, -4.0f}); + const plVec2 leftPos = pl_add_vec2(centerPoint, (plVec2){-4.0f, -4.0f}); + gptDraw->add_triangle_filled(ptWindow->ptFgLayer, pointPos, rightPos, leftPos, (plVec4){1.0f, 1.0f, 1.0f, 1.0f}); + } + else + { + const plVec2 centerPoint = {tStartPos.x + 8.0f * 1.5f, tStartPos.y + tWidgetSize.y / 2.0f}; + const plVec2 pointPos = pl_add_vec2(centerPoint, (plVec2){ 4.0f, 0.0f}); + const plVec2 rightPos = pl_add_vec2(centerPoint, (plVec2){ -4.0f, -4.0f}); + const plVec2 leftPos = pl_add_vec2(centerPoint, (plVec2){ -4.0f, 4.0f}); + gptDraw->add_triangle_filled(ptWindow->ptFgLayer, pointPos, rightPos, leftPos, (plVec4){1.0f, 1.0f, 1.0f, 1.0f}); + } + + const plVec2 tTextStartPos = { + .x = tStartPos.x + tWidgetSize.y * 1.5f, + .y = tStartPos.y + tStartPos.y + tWidgetSize.y / 2.0f - tTextActualCenter.y + }; + pl_ui_add_text(ptWindow->ptFgLayer, gptCtx->ptFont, gptCtx->tStyle.fFontSize, tTextStartPos, gptCtx->tColorScheme.tTextCol, pcText, -1.0f); + } + pl_advance_cursor(tWidgetSize.x, tWidgetSize.y); + + if(*pbOpenState) + { + ptWindow->tTempData.uTreeDepth++; + pl_sb_push(ptWindow->sbtRowStack, ptWindow->tTempData.tCurrentLayoutRow); + pl_sb_push(gptCtx->sbuIdStack, uHash); + } + + return *pbOpenState; +} + +bool +pl_tree_node_v(const char* pcFmt, va_list args) +{ + static char acTempBuffer[1024]; + pl_vsprintf(acTempBuffer, pcFmt, args); + return pl_tree_node(acTempBuffer); +} + +bool +pl_tree_node_f(const char* pcFmt, ...) +{ + va_list args; + va_start(args, pcFmt); + bool bOpen = pl_tree_node_v(pcFmt, args); + va_end(args); + return bOpen; +} + +void +pl_tree_pop(void) +{ + plUiWindow* ptWindow = gptCtx->ptCurrentWindow; + ptWindow->tTempData.uTreeDepth--; + pl_sb_pop(gptCtx->sbuIdStack); + ptWindow->tTempData.tCurrentLayoutRow = pl_sb_pop(ptWindow->sbtRowStack); +} + +bool +pl_begin_tab_bar(const char* pcText) +{ + plUiWindow* ptWindow = gptCtx->ptCurrentWindow; + const float fFrameHeight = pl_get_frame_height(); + + const plVec2 tStartPos = pl__ui_get_cursor_pos(); + const plVec2 tWidgetSize = pl_calculate_item_size(pl_get_frame_height()); + + pl_sb_push(ptWindow->sbtRowStack, ptWindow->tTempData.tCurrentLayoutRow); + ptWindow->tTempData.tCurrentLayoutRow.fRowStartX = tStartPos.x - ptWindow->tTempData.tRowPos.x; + ptWindow->tTempData.fAccumRowX += ptWindow->tTempData.tCurrentLayoutRow.fRowStartX; + + pl_layout_dynamic(0.0f, 1); + plUiLayoutRow* ptCurrentRow = &ptWindow->tTempData.tCurrentLayoutRow; + + const uint32_t uHash = pl_str_hash(pcText, 0, pl_sb_top(gptCtx->sbuIdStack)); + pl_sb_push(gptCtx->sbuIdStack, uHash); + + // check if tab bar existed + gptCtx->ptCurrentTabBar = NULL; + for(uint32_t i = 0; i < pl_sb_size(gptCtx->sbtTabBars); i++) + { + if(gptCtx->sbtTabBars[i].uId == uHash) + { + gptCtx->ptCurrentTabBar = &gptCtx->sbtTabBars[i]; + break; + } + } + + // new tab bar needs to be created + if(gptCtx->ptCurrentTabBar == NULL) + { + plUiTabBar tTabBar = { + .uId = uHash + };; + + pl_sb_push(gptCtx->sbtTabBars, tTabBar); + gptCtx->ptCurrentTabBar = &pl_sb_top(gptCtx->sbtTabBars); + } + + gptCtx->ptCurrentTabBar->tStartPos = tStartPos; + gptCtx->ptCurrentTabBar->tCursorPos = tStartPos; + gptCtx->ptCurrentTabBar->uCurrentIndex = 0u; + + gptDraw->add_line(ptWindow->ptFgLayer, + (plVec2){gptCtx->ptCurrentTabBar->tStartPos.x, gptCtx->ptCurrentTabBar->tStartPos.y + fFrameHeight}, + (plVec2){gptCtx->ptCurrentTabBar->tStartPos.x + tWidgetSize.x, gptCtx->ptCurrentTabBar->tStartPos.y + fFrameHeight}, + gptCtx->tColorScheme.tButtonActiveCol, 1.0f); + + pl_advance_cursor(tWidgetSize.x, fFrameHeight); + return true; +} + +void +pl_end_tab_bar(void) +{ + plUiWindow* ptWindow = gptCtx->ptCurrentWindow; + gptCtx->ptCurrentTabBar->uValue = gptCtx->ptCurrentTabBar->uNextValue; + pl_sb_pop(gptCtx->sbuIdStack); + ptWindow->tTempData.tCurrentLayoutRow = pl_sb_pop(ptWindow->sbtRowStack); + ptWindow->tTempData.fAccumRowX -= ptWindow->tTempData.tCurrentLayoutRow.fRowStartX; +} + +void +pl_end_tab(void) +{ + plUiWindow* ptWindow = gptCtx->ptCurrentWindow; + pl_sb_pop(gptCtx->sbuIdStack); + ptWindow->tTempData.tCurrentLayoutRow = pl_sb_pop(ptWindow->sbtRowStack); +} + +bool +pl_begin_tab(const char* pcText) +{ + plUiWindow* ptWindow = gptCtx->ptCurrentWindow; + pl_sb_push(ptWindow->sbtRowStack, ptWindow->tTempData.tCurrentLayoutRow); + pl_layout_dynamic(0.0f, 1); + plUiTabBar* ptTabBar = gptCtx->ptCurrentTabBar; + const float fFrameHeight = pl_get_frame_height(); + const uint32_t uHash = pl_str_hash(pcText, 0, pl_sb_top(gptCtx->sbuIdStack)); + pl_sb_push(gptCtx->sbuIdStack, uHash); + + if(ptTabBar->uValue == 0u) ptTabBar->uValue = uHash; + + const plVec2 tTextSize = pl_ui_calculate_text_size(gptCtx->ptFont, gptCtx->tStyle.fFontSize, pcText, -1.0f); + const plVec2 tStartPos = ptTabBar->tCursorPos; + + plRect tTextBounding = gptDraw->calculate_text_bb_ex(gptCtx->ptFont, gptCtx->tStyle.fFontSize, tStartPos, pcText, pl_find_renderered_text_end(pcText, NULL), -1.0f); + const plVec2 tTextActualCenter = pl_rect_center(&tTextBounding); + + const plVec2 tFinalSize = {tTextSize.x + 2.0f * gptCtx->tStyle.tFramePadding.x, fFrameHeight}; + + const plVec2 tTextStartPos = { + .x = tStartPos.x + tStartPos.x + tFinalSize.x / 2.0f - tTextActualCenter.x, + .y = tStartPos.y + tStartPos.y + fFrameHeight / 2.0f - tTextActualCenter.y + }; + + const plRect tBoundingBox = pl_calculate_rect(tStartPos, tFinalSize); + bool bHovered = false; + bool bHeld = false; + const bool bPressed = pl_button_behavior(&tBoundingBox, uHash, &bHovered, &bHeld); + + if(uHash == gptCtx->uActiveId) + { + ptTabBar->uNextValue = uHash; + } + + if(gptCtx->uActiveId == uHash) gptDraw->add_rect_filled(ptWindow->ptFgLayer, tStartPos, tBoundingBox.tMax, gptCtx->tColorScheme.tButtonActiveCol); + else if(gptCtx->uHoveredId == uHash) gptDraw->add_rect_filled(ptWindow->ptFgLayer, tStartPos, tBoundingBox.tMax, gptCtx->tColorScheme.tButtonHoveredCol); + else if(ptTabBar->uValue == uHash) gptDraw->add_rect_filled(ptWindow->ptFgLayer, tStartPos, tBoundingBox.tMax, gptCtx->tColorScheme.tButtonActiveCol); + else gptDraw->add_rect_filled(ptWindow->ptFgLayer, tStartPos, tBoundingBox.tMax, gptCtx->tColorScheme.tButtonCol); + + pl_ui_add_text(ptWindow->ptFgLayer, gptCtx->ptFont, gptCtx->tStyle.fFontSize, tTextStartPos, gptCtx->tColorScheme.tTextCol, pcText, -1.0f); + + ptTabBar->tCursorPos.x += gptCtx->tStyle.tInnerSpacing.x + tFinalSize.x; + ptTabBar->uCurrentIndex++; + + if(ptTabBar->uValue != uHash) + pl_end_tab(); + + return ptTabBar->uValue == uHash; +} + +void +pl_separator(void) +{ + plUiWindow* ptWindow = gptCtx->ptCurrentWindow; + plUiLayoutRow* ptCurrentRow = &ptWindow->tTempData.tCurrentLayoutRow; + + const plVec2 tStartPos = pl__ui_get_cursor_pos(); + const plVec2 tWidgetSize = pl_calculate_item_size(gptCtx->tStyle.tItemSpacing.y * 2.0f); + if(pl__ui_should_render(&tStartPos, &tWidgetSize)) + gptDraw->add_line(ptWindow->ptFgLayer, tStartPos, (plVec2){tStartPos.x + tWidgetSize.x, tStartPos.y}, gptCtx->tColorScheme.tCheckmarkCol, 1.0f); + + pl_advance_cursor(tWidgetSize.x, tWidgetSize.y); +} + +void +pl_vertical_spacing(void) +{ + plUiWindow* ptWindow = gptCtx->ptCurrentWindow; + ptWindow->tTempData.tRowPos.y += gptCtx->tStyle.tItemSpacing.y * 2.0f; +} + +void +pl_indent(float fIndent) +{ + plUiWindow* ptWindow = gptCtx->ptCurrentWindow; + plUiLayoutRow* ptCurrentRow = &ptWindow->tTempData.tCurrentLayoutRow; + ptCurrentRow->fHorizontalOffset += fIndent == 0.0f ? gptCtx->tStyle.fIndentSize : fIndent; + ptWindow->tTempData.fExtraIndent += fIndent == 0.0f ? gptCtx->tStyle.fIndentSize : fIndent; +} + +void +pl_unindent(float fIndent) +{ + plUiWindow* ptWindow = gptCtx->ptCurrentWindow; + plUiLayoutRow* ptCurrentRow = &ptWindow->tTempData.tCurrentLayoutRow; + ptCurrentRow->fHorizontalOffset -= fIndent == 0.0f ? gptCtx->tStyle.fIndentSize : fIndent; + ptWindow->tTempData.fExtraIndent -= fIndent == 0.0f ? gptCtx->tStyle.fIndentSize : fIndent; +} + +void +pl_text_v(const char* pcFmt, va_list args) +{ + static char acTempBuffer[1024]; + + plUiWindow* ptWindow = gptCtx->ptCurrentWindow; + plUiLayoutRow* ptCurrentRow = &ptWindow->tTempData.tCurrentLayoutRow; + const plVec2 tWidgetSize = pl_calculate_item_size(pl_get_frame_height()); + const plVec2 tStartPos = pl__ui_get_cursor_pos(); + + if(pl__ui_should_render(&tStartPos, &tWidgetSize)) + { + pl_vsprintf(acTempBuffer, pcFmt, args); + const plRect tTextBounding = gptDraw->calculate_text_bb_ex(gptCtx->ptFont, gptCtx->tStyle.fFontSize, tStartPos, acTempBuffer, pl_find_renderered_text_end(acTempBuffer, NULL), -1.0f); + const plVec2 tTextActualCenter = pl_rect_center(&tTextBounding); + pl_ui_add_text(ptWindow->ptFgLayer, gptCtx->ptFont, gptCtx->tStyle.fFontSize, (plVec2){tStartPos.x, tStartPos.y + tStartPos.y + tWidgetSize.y / 2.0f - tTextActualCenter.y}, gptCtx->tColorScheme.tTextCol, acTempBuffer, -1.0f); + } + pl_advance_cursor(tWidgetSize.x, tWidgetSize.y); +} + +void +pl_text(const char* pcFmt, ...) +{ + va_list args; + va_start(args, pcFmt); + pl_text_v(pcFmt, args); + va_end(args); +} + +void +pl_color_text_v(plVec4 tColor, const char* pcFmt, va_list args) +{ + static char acTempBuffer[1024]; + + plUiWindow* ptWindow = gptCtx->ptCurrentWindow; + const plVec2 tWidgetSize = pl_calculate_item_size(pl_get_frame_height()); + const plVec2 tStartPos = pl__ui_get_cursor_pos(); + if(pl__ui_should_render(&tStartPos, &tWidgetSize)) + { + pl_vsprintf(acTempBuffer, pcFmt, args); + const plRect tTextBounding = gptDraw->calculate_text_bb_ex(gptCtx->ptFont, gptCtx->tStyle.fFontSize, tStartPos, acTempBuffer, pl_find_renderered_text_end(acTempBuffer, NULL), -1.0f); + const plVec2 tTextActualCenter = pl_rect_center(&tTextBounding); + pl_ui_add_text(ptWindow->ptFgLayer, gptCtx->ptFont, gptCtx->tStyle.fFontSize, (plVec2){tStartPos.x, tStartPos.y + tStartPos.y + tWidgetSize.y / 2.0f - tTextActualCenter.y}, tColor, acTempBuffer, -1.0f); + } + pl_advance_cursor(tWidgetSize.x, tWidgetSize.y); +} + +void +pl_color_text(plVec4 tColor, const char* pcFmt, ...) +{ + va_list args; + va_start(args, pcFmt); + pl_color_text_v(tColor, pcFmt, args); + va_end(args); +} + +void +pl_labeled_text_v(const char* pcLabel, const char* pcFmt, va_list args) +{ + static char acTempBuffer[1024]; + plUiWindow* ptWindow = gptCtx->ptCurrentWindow; + const plVec2 tWidgetSize = pl_calculate_item_size(pl_get_frame_height()); + const plVec2 tStartPos = pl__ui_get_cursor_pos(); + if(pl__ui_should_render(&tStartPos, &tWidgetSize)) + { + pl_vsprintf(acTempBuffer, pcFmt, args); + const plRect tTextBounding = gptDraw->calculate_text_bb_ex(gptCtx->ptFont, gptCtx->tStyle.fFontSize, tStartPos, acTempBuffer, pl_find_renderered_text_end(acTempBuffer, NULL), -1.0f); + const plVec2 tTextActualCenter = pl_rect_center(&tTextBounding); + + const plVec2 tStartLocation = {tStartPos.x, tStartPos.y + tStartPos.y + tWidgetSize.y / 2.0f - tTextActualCenter.y}; + pl_ui_add_text(ptWindow->ptFgLayer, gptCtx->ptFont, gptCtx->tStyle.fFontSize, pl_add_vec2(tStartLocation, (plVec2){(tWidgetSize.x / 3.0f), 0.0f}), gptCtx->tColorScheme.tTextCol, acTempBuffer, -1.0f); + pl_ui_add_text(ptWindow->ptFgLayer, gptCtx->ptFont, gptCtx->tStyle.fFontSize, tStartLocation, gptCtx->tColorScheme.tTextCol, pcLabel, -1.0f); + } + pl_advance_cursor(tWidgetSize.x, tWidgetSize.y); +} + +void +pl_labeled_text(const char* pcLabel, const char* pcFmt, ...) +{ + va_list args; + va_start(args, pcFmt); + pl_labeled_text_v(pcLabel, pcFmt, args); + va_end(args); +} + +bool +pl_input_text_ex(const char* pcLabel, const char* pcHint, char* pcBuffer, size_t szBufferSize, plUiInputTextFlags tFlags); + +bool +pl_input_text(const char* pcLabel, char* pcBuffer, size_t szBufferSize) +{ + return pl_input_text_ex(pcLabel, NULL, pcBuffer, szBufferSize, 0); +} + +bool +pl_input_text_hint(const char* pcLabel, const char* pcHint, char* pcBuffer, size_t szBufferSize) +{ + return pl_input_text_ex(pcLabel, pcHint, pcBuffer, szBufferSize, 0); +} + +bool +pl_input_float(const char* pcLabel, float* pfValue, const char* pcFormat) +{ + char acBuffer[64] = {0}; + pl_sprintf(acBuffer, pcFormat, *pfValue); + const bool bChanged = pl_input_text_ex(pcLabel, NULL, acBuffer, 64, PL_UI_INPUT_TEXT_FLAGS_CHARS_SCIENTIFIC); + + if(bChanged) + *pfValue = (float)atof(acBuffer); + + return bChanged; +} + +bool +pl_input_int(const char* pcLabel, int* piValue) +{ + char acBuffer[64] = {0}; + pl_sprintf(acBuffer, "%d", *piValue); + const bool bChanged = pl_input_text_ex(pcLabel, NULL, acBuffer, 64, PL_UI_INPUT_TEXT_FLAGS_CHARS_DECIMAL); + + if(bChanged) + *piValue = atoi(acBuffer); + + return bChanged; +} + +bool +pl_input_text_ex(const char* pcLabel, const char* pcHint, char* pcBuffer, size_t szBufferSize, plUiInputTextFlags tFlags) +{ + plUiWindow* ptWindow = gptCtx->ptCurrentWindow; + const plVec2 tMousePos = gptIOI->get_mouse_pos(); + + const bool RENDER_SELECTION_WHEN_INACTIVE = false; + const bool bIsMultiLine = (tFlags & PL_UI_INPUT_TEXT_FLAGS_MULTILINE) != 0; + const bool bIsReadOnly = (tFlags & PL_UI_INPUT_TEXT_FLAGS_READ_ONLY) != 0; + const bool bIsPassword = (tFlags & PL_UI_INPUT_TEXT_FLAGS_PASSWORD) != 0; + const bool bIsUndoable = (tFlags & PL_UI_INPUT_TEXT_FLAGS_NO_UNDO_REDO) != 0; + const bool bIsResizable = (tFlags & PL_UI_INPUT_TEXT_FLAGS_CALLBACK_RESIZE) != 0; + + const plVec2 tWidgetSize = pl_calculate_item_size(pl_get_frame_height()); + const plVec2 tStartPos = pl__ui_get_cursor_pos(); + + const plVec2 tFrameStartPos = {floorf(tStartPos.x + (tWidgetSize.x / 3.0f)), tStartPos.y }; + const uint32_t uHash = pl_str_hash(pcLabel, 0, pl_sb_top(gptCtx->sbuIdStack)); + + const plRect tLabelTextBounding = gptDraw->calculate_text_bb_ex(gptCtx->ptFont, gptCtx->tStyle.fFontSize, tFrameStartPos, pcLabel, pl_find_renderered_text_end(pcLabel, NULL), -1.0f); + const plVec2 tLabelTextActualCenter = pl_rect_center(&tLabelTextBounding); + + const plVec2 tFrameSize = { 2.0f * (tWidgetSize.x / 3.0f), tWidgetSize.y}; + plRect tBoundingBox = pl_calculate_rect(tFrameStartPos, tFrameSize); + const plRect* ptClipRect = gptDraw->get_clip_rect(gptCtx->ptDrawlist); + tBoundingBox = pl_rect_clip_full(&tBoundingBox, ptClipRect); + + plUiWindow* ptDrawWindow = ptWindow; + plVec2 tInnerSize = tFrameSize; + + const bool bHovered = pl_is_item_hoverable(&tBoundingBox, uHash); + + if(bHovered) + { + gptIOI->set_mouse_cursor(PL_MOUSE_CURSOR_TEXT_INPUT); + gptCtx->uNextHoveredId = uHash; + } + + plUiInputTextState* ptState = pl__get_input_text_state(uHash); + + // TODO: scroll stuff + const bool bUserClicked = bHovered && gptIOI->is_mouse_clicked(0, false); + const bool bUserScrollFinish = false; // bIsMultiLine && ptState != NULL && gptCtx->uNextActiveId == 0 && gptCtx->uActiveId == GetWindowScrollbarID(ptDrawWindow, ImGuiAxis_Y); + const bool bUserScrollActive = false; // bIsMultiLine && ptState != NULL && gptCtx->uNextActiveId == GetWindowScrollbarID(ptDrawWindow, ImGuiAxis_Y); + + bool bClearActiveId = false; + bool bSelectAll = false; + + float fScrollY = bIsMultiLine ? ptDrawWindow->tScroll.y : FLT_MAX; + + const bool bInitChangedSpecs = (ptState != NULL && ptState->tStb.single_line != !bIsMultiLine); // state != NULL means its our state. + const bool bInitMakeActive = (bUserClicked || bUserScrollFinish); + const bool bInitState = (bInitMakeActive || bUserScrollActive); + if((bUserClicked && gptCtx->uActiveId != uHash) || bInitChangedSpecs) + { + // Access state even if we don't own it yet. + ptState = &gptCtx->tInputTextState; + pl__text_state_cursor_anim_reset(ptState); + + // Take a copy of the initial buffer value (both in original UTF-8 format and converted to wchar) + // From the moment we focused we are ignoring the content of 'buf' (unless we are in read-only mode) + const int iBufferLength = (int)strlen(pcBuffer); + pl_sb_resize(ptState->sbInitialTextA, iBufferLength + 1); // UTF-8. we use +1 to make sure that it is always pointing to at least an empty string. + memcpy(ptState->sbInitialTextA, pcBuffer, iBufferLength + 1); + + // Preserve cursor position and undo/redo stack if we come back to same widget + // FIXME: Since we reworked this on 2022/06, may want to differenciate recycle_cursor vs recycle_undostate? + bool bRecycleState = (ptState->uId == uHash && !bInitChangedSpecs); + if (bRecycleState && (ptState->iCurrentLengthA != iBufferLength || (ptState->bTextAIsValid && strncmp(ptState->sbTextA, pcBuffer, iBufferLength) != 0))) + bRecycleState = false; + + // start edition + const char* pcBufferEnd = NULL; + ptState->uId = uHash; + pl_sb_resize(ptState->sbTextW, (uint32_t)szBufferSize + 1); // wchar count <= UTF-8 count. we use +1 to make sure that .Data is always pointing to at least an empty string. + pl_sb_reset(ptState->sbTextA); + ptState->bTextAIsValid = false; // TextA is not valid yet (we will display buf until then) + ptState->iCurrentLengthW = pl__text_str_from_utf8(ptState->sbTextW, (int)szBufferSize, pcBuffer, NULL, &pcBufferEnd); + ptState->iCurrentLengthA = (int)(pcBufferEnd - pcBuffer); // We can't get the result from ImStrncpy() above because it is not UTF-8 aware. Here we'll cut off malformed UTF-8. + + if (bRecycleState) + { + // Recycle existing cursor/selection/undo stack but clamp position + // Note a single mouse click will override the cursor/position immediately by calling stb_textedit_click handler. + pl__text_state_cursor_clamp(ptState); + } + else + { + ptState->fScrollX = 0.0f; + stb_textedit_initialize_state(&ptState->tStb, !bIsMultiLine); + } + + if (!bIsMultiLine) + { + if (tFlags & PL_UI_INPUT_TEXT_FLAGS_AUTO_SELECT_ALL) + bSelectAll = true; + // if (input_requested_by_nav && (!bRecycleState || !(g.NavActivateFlags & ImGuiActivateFlags_TryToPreserveState))) + // bSelectAll = true; + // if (input_requested_by_tabbing || (bUserClicked &&gptCtx->tIO.bKeyCtrl)) + // bSelectAll = true; + } + + if (tFlags & PL_UI_INPUT_TEXT_FLAGS_ALWAYS_OVERWRITE) + ptState->tStb.insert_mode = 1; // stb field name is indeed incorrect (see #2863) + + } + + const bool bIsOsX = gptCtx->tIO.bConfigMacOSXBehaviors; + if (gptCtx->uActiveId != uHash && bInitMakeActive) + { + PL_ASSERT(ptState && ptState->uId == uHash); + gptCtx->tPrevItemData.bActive = true; + pl__set_active_id(uHash, ptWindow); + // SetFocusID(id, window); + // FocusWindow(window); + } + + // We have an edge case if ActiveId was set through another widget (e.g. widget being swapped), clear id immediately (don't wait until the end of the function) + if (gptCtx->uActiveId == uHash && ptState == NULL) + pl__set_active_id(0, ptWindow); + + // Release focus when we click outside + if (gptCtx->uActiveId == uHash && gptIOI->is_mouse_clicked(0, false) && !bInitState && !bInitMakeActive) //-V560 + bClearActiveId = true; + + // Lock the decision of whether we are going to take the path displaying the cursor or selection + bool bRenderCursor = (gptCtx->uActiveId == uHash) || (ptState && bUserScrollActive); + bool bRenderSelection = ptState && (pl__text_state_has_selection(ptState) || bSelectAll) && (RENDER_SELECTION_WHEN_INACTIVE || bRenderCursor); + bool bValueChanged = false; + bool bValidated = false; + + // When read-only we always use the live data passed to the function + // FIXME-OPT: Because our selection/cursor code currently needs the wide text we need to convert it when active, which is not ideal :( + if (bIsReadOnly && ptState != NULL && (bRenderCursor || bRenderSelection)) + { + const char* pcBufferEnd = NULL; + pl_sb_resize(ptState->sbTextW, (uint32_t)szBufferSize + 1); + ptState->iCurrentLengthW = pl__text_str_from_utf8(ptState->sbTextW, pl_sb_size(ptState->sbTextW), pcBuffer, NULL, &pcBufferEnd); + ptState->iCurrentLengthA = (int)(pcBufferEnd - pcBuffer); + pl__text_state_cursor_clamp(ptState); + bRenderSelection &= pl__text_state_has_selection(ptState); + } + + // Select the buffer to render. + const bool bBufferDisplayFromState = (bRenderCursor || bRenderSelection || gptCtx->uActiveId == uHash) && !bIsReadOnly && ptState && ptState->bTextAIsValid; + const bool bIsDisplayingHint = (pcHint != NULL && (bBufferDisplayFromState ? ptState->sbTextA : pcBuffer)[0] == 0); + + // Password pushes a temporary font with only a fallback glyph + if (bIsPassword && !bIsDisplayingHint) + { + // const ImFontGlyph* glyph = g.Font->FindGlyph('*'); + // ImFont* password_font = &g.InputTextPasswordFont; + // password_font->FontSize = g.Font->FontSize; + // password_font->Scale = g.Font->Scale; + // password_font->Ascent = g.Font->Ascent; + // password_font->Descent = g.Font->Descent; + // password_font->ContainerAtlas = g.Font->ContainerAtlas; + // password_font->FallbackGlyph = glyph; + // password_font->FallbackAdvanceX = glyph->AdvanceX; + // PL_ASSERT(password_font->Glyphs.empty() && password_font->IndexAdvanceX.empty() && password_font->IndexLookup.empty()); + // PushFont(password_font); + } + + // process mouse inputs and character inputs + int iBackupCurrentTextLength = 0; + if(gptCtx->uActiveId == uHash) + { + PL_ASSERT(ptState != NULL); + iBackupCurrentTextLength = ptState->iCurrentLengthA; + ptState->bEdited = false; + ptState->iBufferCapacityA = (int)szBufferSize; + ptState->tFlags = tFlags; + + // Although we are active we don't prevent mouse from hovering other elements unless we are interacting right now with the widget. + // Down the line we should have a cleaner library-wide concept of Selected vs Active. + // g.ActiveIdAllowOverlap = !io.MouseDown[0]; + + // Edit in progress + const float fMouseX = (tMousePos.x - tBoundingBox.tMin.x - gptCtx->tStyle.tFramePadding.x) + ptState->fScrollX; + const float fMouseY = (bIsMultiLine ? (tMousePos.y - tBoundingBox.tMin.y) : (gptCtx->tStyle.fFontSize * 0.5f)); + + if (bSelectAll) + { + pl__text_state_select_all(ptState); + ptState->bSelectedAllMouseLock = true; + } + else if (bHovered && gptCtx->tIO._auMouseClickedCount[0] >= 2 && !gptCtx->tIO.bKeyShift) + { + stb_textedit_click(ptState, &ptState->tStb, fMouseX, fMouseY); + const int iMultiClipCount = (gptCtx->tIO._auMouseClickedCount[0] - 2); + if ((iMultiClipCount % 2) == 0) + { + // Double-click: Select word + // We always use the "Mac" word advance for double-click select vs CTRL+Right which use the platform dependent variant: + // FIXME: There are likely many ways to improve this behavior, but there's no "right" behavior (depends on use-case, software, OS) + const bool bIsBol = (ptState->tStb.cursor == 0) || STB_TEXTEDIT_GETCHAR(ptState, ptState->tStb.cursor - 1) == '\n'; + if (STB_TEXT_HAS_SELECTION(&ptState->tStb) || !bIsBol) + pl__text_state_on_key_press(ptState, STB_TEXTEDIT_K_WORDLEFT); + if (!STB_TEXT_HAS_SELECTION(&ptState->tStb)) + stb_textedit_prep_selection_at_cursor(&ptState->tStb); + ptState->tStb.cursor = STB_TEXTEDIT_MOVEWORDRIGHT_MAC(ptState, ptState->tStb.cursor); + ptState->tStb.select_end = ptState->tStb.cursor; + stb_textedit_clamp(ptState, &ptState->tStb); + } + else + { + // Triple-click: Select line + const bool is_eol = STB_TEXTEDIT_GETCHAR(ptState, ptState->tStb.cursor) == '\n'; + pl__text_state_on_key_press(ptState, STB_TEXTEDIT_K_LINESTART); + pl__text_state_on_key_press(ptState, STB_TEXTEDIT_K_LINEEND | STB_TEXTEDIT_K_SHIFT); + pl__text_state_on_key_press(ptState, STB_TEXTEDIT_K_RIGHT | STB_TEXTEDIT_K_SHIFT); + if (!is_eol && bIsMultiLine) + { + int iOne = ptState->tStb.select_start; + int iTwo = ptState->tStb.select_end; + ptState->tStb.select_start = iTwo; + ptState->tStb.select_end = iOne; + ptState->tStb.cursor = ptState->tStb.select_end; + } + ptState->bCursorFollow = false; + } + pl__text_state_cursor_anim_reset(ptState); + } + else if (gptCtx->tIO._abMouseClicked[0] && !ptState->bSelectedAllMouseLock) + { + if (bHovered) + { + if (gptCtx->tIO.bKeyShift) + stb_textedit_drag(ptState, &ptState->tStb, fMouseX, fMouseY); + else + stb_textedit_click(ptState, &ptState->tStb, fMouseX, fMouseY); + pl__text_state_cursor_anim_reset(ptState); + } + } + else if (gptCtx->tIO._abMouseDown[0] && !ptState->bSelectedAllMouseLock && (gptCtx->tIO._tMouseDelta.x != 0.0f || gptCtx->tIO._tMouseDelta.y != 0.0f)) + { + stb_textedit_drag(ptState, &ptState->tStb, fMouseX, fMouseY); + pl__text_state_cursor_anim_reset(ptState); + ptState->bCursorFollow = true; + } + if (ptState->bSelectedAllMouseLock && !gptCtx->tIO._abMouseDown[0]) + ptState->bSelectedAllMouseLock = false; + + // We expect backends to emit a Tab key but some also emit a Tab character which we ignore (#2467, #1336) + // (For Tab and Enter: Win32/SFML/Allegro are sending both keys and chars, GLFW and SDL are only sending keys. For Space they all send all threes) + if ((tFlags & PL_UI_INPUT_TEXT_FLAGS_ALLOW_TAB_INPUT) && gptIOI->is_key_down(PL_KEY_TAB) && !bIsReadOnly) + { + unsigned int c = '\t'; // Insert TAB + if (pl__input_text_filter_character(&c, tFlags)) + pl__text_state_on_key_press(ptState, (int)c); + } + + // Process regular text input (before we check for Return because using some IME will effectively send a Return?) + // We ignore CTRL inputs, but need to allow ALT+CTRL as some keyboards (e.g. German) use AltGR (which _is_ Alt+Ctrl) to input certain characters. + const bool bIgnoreCharInputs = (gptCtx->tIO.bKeyCtrl && !gptCtx->tIO.bKeyAlt) || (bIsOsX && gptCtx->tIO.bKeySuper); + + if (pl_sb_size(gptCtx->tIO._sbInputQueueCharacters) > 0) + { + if (!bIgnoreCharInputs && !bIsReadOnly) // && input_requested_by_nav + for (uint32_t n = 0; n < pl_sb_size(gptCtx->tIO._sbInputQueueCharacters); n++) + { + // Insert character if they pass filtering + unsigned int c = (unsigned int)gptCtx->tIO._sbInputQueueCharacters[n]; + if (c == '\t') // Skip Tab, see above. + continue; + if (pl__input_text_filter_character(&c, tFlags)) + pl__text_state_on_key_press(ptState, c); + } + + // consume characters + pl_sb_reset(gptCtx->tIO._sbInputQueueCharacters) + } + } + + // Process other shortcuts/key-presses + bool bRevertEdit = false; + if (gptCtx->uActiveId == uHash && !gptCtx->bActiveIdJustActivated && !bClearActiveId) + { + PL_ASSERT(ptState != NULL); + + const int iRowCountPerPage = pl_max((int)((tInnerSize.y - gptCtx->tStyle.tFramePadding.y) / gptCtx->tStyle.fFontSize), 1); + ptState->tStb.row_count_per_page = iRowCountPerPage; + + const int iKMask = (gptCtx->tIO.bKeyShift ? STB_TEXTEDIT_K_SHIFT : 0); + const bool bIsWordmoveKeyDown = bIsOsX ? gptCtx->tIO.bKeyAlt : gptCtx->tIO.bKeyCtrl; // OS X style: Text editing cursor movement using Alt instead of Ctrl + const bool bIsStartendKeyDown = bIsOsX && gptCtx->tIO.bKeySuper && !gptCtx->tIO.bKeyCtrl && !gptCtx->tIO.bKeyAlt; // OS X style: Line/Text Start and End using Cmd+Arrows instead of Home/End + + // Using Shortcut() with ImGuiInputFlags_RouteFocused (default policy) to allow routing operations for other code (e.g. calling window trying to use CTRL+A and CTRL+B: formet would be handled by InputText) + // Otherwise we could simply assume that we own the keys as we are active. + // const ImGuiInputFlags bRepeat = ImGuiInputFlags_Repeat; + const bool bRepeat = false; + const bool bIsCut = (gptCtx->tIO.bKeyCtrl && gptIOI->is_key_pressed(PL_KEY_X, bRepeat)) || (gptCtx->tIO.bKeyShift && gptIOI->is_key_pressed(PL_KEY_DELETE, bRepeat)) && !bIsReadOnly && !bIsPassword && (!bIsMultiLine || pl__text_state_has_selection(ptState)); + const bool bIsCopy = (gptCtx->tIO.bKeyCtrl && gptIOI->is_key_pressed(PL_KEY_C, bRepeat)) || (gptCtx->tIO.bKeyCtrl && gptIOI->is_key_pressed(PL_KEY_INSERT, bRepeat)) && !bIsPassword && (!bIsMultiLine || pl__text_state_has_selection(ptState)); + const bool bIsPaste = (gptCtx->tIO.bKeyCtrl && gptIOI->is_key_pressed(PL_KEY_V, bRepeat)) || ((gptCtx->tIO.bKeyShift && gptIOI->is_key_pressed(PL_KEY_INSERT, bRepeat)) && !bIsReadOnly); + const bool bIsUndo = (gptCtx->tIO.bKeyCtrl && gptIOI->is_key_pressed(PL_KEY_Z, bRepeat)) && !bIsReadOnly && bIsUndoable; + const bool bIsRedo = (gptCtx->tIO.bKeyCtrl && gptIOI->is_key_pressed(PL_KEY_Y, bRepeat)) || (bIsOsX && gptCtx->tIO.bKeyShift && gptCtx->tIO.bKeyCtrl && gptIOI->is_key_pressed(PL_KEY_Z, bRepeat)) && !bIsReadOnly && bIsUndoable; + const bool bIsSelectAll = gptCtx->tIO.bKeyCtrl && gptIOI->is_key_pressed(PL_KEY_A, bRepeat); + + // We allow validate/cancel with Nav source (gamepad) to makes it easier to undo an accidental NavInput press with no keyboard wired, but otherwise it isn't very useful. + const bool bNavGamepadActive = false; // (io.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) != 0 && (io.BackendFlags & ImGuiBackendFlags_HasGamepad) != 0; + const bool bIsEnterPressed = gptIOI->is_key_pressed(PL_KEY_ENTER, true) || gptIOI->is_key_pressed(PL_KEY_KEYPAD_ENTER, true); + const bool bIsGamepadValidate = false; // nav_gamepad_active && (gptIOI->is_key_pressed(PL_KEY_NavGamepadActivate, false) || gptIOI->is_key_pressed(PL_KEY_NavGamepadInput, false)); + const bool bIsCancel = gptIOI->is_key_pressed(PL_KEY_ESCAPE, bRepeat); // Shortcut(PL_KEY_Escape, id, bRepeat) || (nav_gamepad_active && Shortcut(PL_KEY_NavGamepadCancel, uHash, bRepeat)); + + // FIXME: Should use more Shortcut() and reduce gptIOI->is_key_pressed()+SetKeyOwner(), but requires modifiers combination to be taken account of. + if (gptIOI->is_key_pressed(PL_KEY_LEFT_ARROW, true)) { pl__text_state_on_key_press(ptState, (bIsStartendKeyDown ? STB_TEXTEDIT_K_LINESTART : bIsWordmoveKeyDown ? STB_TEXTEDIT_K_WORDLEFT : STB_TEXTEDIT_K_LEFT) | iKMask); } + else if (gptIOI->is_key_pressed(PL_KEY_RIGHT_ARROW, true)) { pl__text_state_on_key_press(ptState, (bIsStartendKeyDown ? STB_TEXTEDIT_K_LINEEND : bIsWordmoveKeyDown ? STB_TEXTEDIT_K_WORDRIGHT : STB_TEXTEDIT_K_RIGHT) | iKMask); } + // else if (gptIOI->is_key_pressed(PL_KEY_UP_ARROW, true) && bIsMultiLine) { if (gptCtx->tIO.bKeyCtrl) SetScrollY(ptDrawWindow, pl_max(ptDrawWindow->tScroll.y - gptCtx->tStyle.fFontSize, 0.0f)); else pl__text_state_on_key_press(ptState, (bIsStartendKeyDown ? STB_TEXTEDIT_K_TEXTSTART : STB_TEXTEDIT_K_UP) | iKMask); } + // else if (gptIOI->is_key_pressed(PL_KEY_DOWN_ARROW, true) && bIsMultiLine) { if (gptCtx->tIO.bKeyCtrl) SetScrollY(ptDrawWindow, pl_min(ptDrawWindow->tScroll.y + gptCtx->tStyle.fFontSize, GetScrollMaxY())); else pl__text_state_on_key_press(ptState, (bIsStartendKeyDown ? STB_TEXTEDIT_K_TEXTEND : STB_TEXTEDIT_K_DOWN) | iKMask); } + // else if (gptIOI->is_key_pressed(PL_KEY_PAGE_UP, true) && bIsMultiLine) { pl__text_state_on_key_press(ptState, STB_TEXTEDIT_K_PGUP | iKMask); fScrollY -= iRowCountPerPage * gptCtx->tStyle.fFontSize; } + // else if (gptIOI->is_key_pressed(PL_KEY_PAGE_DOWN, true) && bIsMultiLine) { pl__text_state_on_key_press(ptState, STB_TEXTEDIT_K_PGDOWN | iKMask); fScrollY += iRowCountPerPage * gptCtx->tStyle.fFontSize; } + else if (gptIOI->is_key_pressed(PL_KEY_HOME, true)) { pl__text_state_on_key_press(ptState,gptCtx->tIO.bKeyCtrl ? STB_TEXTEDIT_K_TEXTSTART | iKMask : STB_TEXTEDIT_K_LINESTART | iKMask); } + else if (gptIOI->is_key_pressed(PL_KEY_END, true)) { pl__text_state_on_key_press(ptState,gptCtx->tIO.bKeyCtrl ? STB_TEXTEDIT_K_TEXTEND | iKMask : STB_TEXTEDIT_K_LINEEND | iKMask); } + else if (gptIOI->is_key_pressed(PL_KEY_DELETE, true) && !bIsReadOnly && !bIsCut) + { + if (!pl__text_state_has_selection(ptState)) + { + // OSX doesn't seem to have Super+Delete to delete until end-of-line, so we don't emulate that (as opposed to Super+Backspace) + if (bIsWordmoveKeyDown) + pl__text_state_on_key_press(ptState, STB_TEXTEDIT_K_WORDRIGHT | STB_TEXTEDIT_K_SHIFT); + } + pl__text_state_on_key_press(ptState, STB_TEXTEDIT_K_DELETE | iKMask); + } + else if (gptIOI->is_key_pressed(PL_KEY_BACKSPACE, true) && !bIsReadOnly) + { + if (!pl__text_state_has_selection(ptState)) + { + if (bIsWordmoveKeyDown) + pl__text_state_on_key_press(ptState, STB_TEXTEDIT_K_WORDLEFT | STB_TEXTEDIT_K_SHIFT); + else if (bIsOsX && gptCtx->tIO.bKeySuper && !gptCtx->tIO.bKeyAlt && !gptCtx->tIO.bKeyCtrl) + pl__text_state_on_key_press(ptState, STB_TEXTEDIT_K_LINESTART | STB_TEXTEDIT_K_SHIFT); + } + pl__text_state_on_key_press(ptState, STB_TEXTEDIT_K_BACKSPACE | iKMask); + } + else if (bIsEnterPressed || bIsGamepadValidate) + { + // Determine if we turn Enter into a \n character + bool bCtrlEnterForNewLine = (tFlags & PL_UI_INPUT_TEXT_FLAGS_CTRL_ENTER_FOR_NEW_LINE) != 0; + if (!bIsMultiLine || bIsGamepadValidate || (bCtrlEnterForNewLine && !gptCtx->tIO.bKeyCtrl) || (!bCtrlEnterForNewLine && gptCtx->tIO.bKeyCtrl)) + { + bValidated = true; + // if (io.ConfigInputTextEnterKeepActive && !bIsMultiLine) + // pl__text_state_select_all(ptState); // No need to scroll + // else + bClearActiveId = true; + } + else if (!bIsReadOnly) + { + unsigned int c = '\n'; // Insert new line + if (pl__input_text_filter_character(&c, tFlags)) + pl__text_state_on_key_press(ptState, (int)c); + } + } + else if (bIsCancel) + { + if (tFlags & PL_UI_INPUT_TEXT_FLAGS_ESCAPE_CLEARS_ALL) + { + if (ptState->iCurrentLengthA > 0) + { + bRevertEdit = true; + } + else + { + bRenderCursor = bRenderSelection = false; + bClearActiveId = true; + } + } + else + { + bClearActiveId = bRevertEdit = true; + bRenderCursor = bRenderSelection = false; + } + } + else if (bIsUndo || bIsRedo) + { + pl__text_state_on_key_press(ptState, bIsUndo ? STB_TEXTEDIT_K_UNDO : STB_TEXTEDIT_K_REDO); + pl__text_state_clear_selection(ptState); + } + else if (bIsSelectAll) + { + pl__text_state_select_all(ptState); + ptState->bCursorFollow = true; + } + else if (bIsCut || bIsCopy) + { + // Cut, Copy + if (gptCtx->tIO.set_clipboard_text_fn) + { + const int ib = pl__text_state_has_selection(ptState) ? pl_min(ptState->tStb.select_start, ptState->tStb.select_end) : 0; + const int ie = pl__text_state_has_selection(ptState) ? pl_max(ptState->tStb.select_start, ptState->tStb.select_end) : ptState->iCurrentLengthW; + const int clipboard_data_len = pl__text_count_utf8_bytes_from_str(ptState->sbTextW + ib, ptState->sbTextW + ie) + 1; + char* clipboard_data = (char*)PL_ALLOC(clipboard_data_len * sizeof(char)); + pl__text_str_to_utf8(clipboard_data, clipboard_data_len, ptState->sbTextW + ib, ptState->sbTextW + ie); + gptCtx->tIO.set_clipboard_text_fn(gptCtx->tIO.pClipboardUserData, clipboard_data); + PL_FREE(clipboard_data); + } + if (bIsCut) + { + if (!pl__text_state_has_selection(ptState)) + pl__text_state_select_all(ptState); + ptState->bCursorFollow = true; + stb_textedit_cut(ptState, &ptState->tStb); + } + } + else if (bIsPaste) + { + const char* clipboard = gptCtx->tIO.get_clipboard_text_fn(gptCtx->tIO.pClipboardUserData); + if (clipboard) + { + // Filter pasted buffer + const int clipboard_len = (int)strlen(clipboard); + plUiWChar* clipboard_filtered = (plUiWChar*)PL_ALLOC((clipboard_len + 1) * sizeof(plUiWChar)); + int clipboard_filtered_len = 0; + for (const char* s = clipboard; *s != 0; ) + { + unsigned int c; + s += pl_text_char_from_utf8(&c, s, NULL); + if (!pl__input_text_filter_character(&c, tFlags)) + continue; + clipboard_filtered[clipboard_filtered_len++] = (plUiWChar)c; //-V522 + } + clipboard_filtered[clipboard_filtered_len] = 0; //-V522 + if (clipboard_filtered_len > 0) // If everything was filtered, ignore the pasting operation + { + stb_textedit_paste(ptState, &ptState->tStb, clipboard_filtered, clipboard_filtered_len); + ptState->bCursorFollow = true; + } + PL_FREE(clipboard_filtered); + } + } + + // Update render selection flag after events have been handled, so selection highlight can be displayed during the same frame. + bRenderSelection |= pl__text_state_has_selection(ptState) && (RENDER_SELECTION_WHEN_INACTIVE || bRenderCursor); + } + + // Process callbacks and apply result back to user's buffer. + const char* pcApplyNewText = NULL; + int iApplyNewTextLength = 0; + if (gptCtx->uActiveId == uHash) + { + PL_ASSERT(ptState != NULL); + if (bRevertEdit && !bIsReadOnly) + { + if (tFlags & PL_UI_INPUT_TEXT_FLAGS_ESCAPE_CLEARS_ALL) + { + // Clear input + pcApplyNewText = ""; + iApplyNewTextLength = 0; + STB_TEXTEDIT_CHARTYPE empty_string = 0; + stb_textedit_replace(ptState, &ptState->tStb, &empty_string, 0); + } + else if (strcmp(pcBuffer, ptState->sbInitialTextA) != 0) + { + // Restore initial value. Only return true if restoring to the initial value changes the current buffer contents. + // Push records into the undo stack so we can CTRL+Z the revert operation itself + pcApplyNewText = ptState->sbInitialTextA; + iApplyNewTextLength = pl_sb_size(ptState->sbInitialTextA) - 1; + bValueChanged = true; + plUiWChar* sbWText = NULL; + if (iApplyNewTextLength > 0) + { + pl_sb_resize(sbWText, pl__text_count_chars_from_utf8(pcApplyNewText, pcApplyNewText + iApplyNewTextLength) + 1); + pl__text_str_from_utf8(sbWText, pl_sb_size(sbWText), pcApplyNewText, pcApplyNewText + iApplyNewTextLength, NULL); + } + stb_textedit_replace(ptState, &ptState->tStb, sbWText, (iApplyNewTextLength > 0) ? (pl_sb_size(sbWText) - 1) : 0); + } + } + + // Apply ASCII value + if (!bIsReadOnly) + { + ptState->bTextAIsValid = true; + pl_sb_resize(ptState->sbTextA, pl_sb_size(ptState->sbTextW) * 4 + 1); + pl__text_str_to_utf8(ptState->sbTextA, pl_sb_size(ptState->sbTextA), ptState->sbTextW, NULL); + } + + // When using 'ImGuiInputTextFlags_EnterReturnsTrue' as a special case we reapply the live buffer back to the input buffer before clearing ActiveId, even though strictly speaking it wasn't modified on this frame. + // If we didn't do that, code like InputInt() with ImGuiInputTextFlags_EnterReturnsTrue would fail. + // This also allows the user to use InputText() with ImGuiInputTextFlags_EnterReturnsTrue without maintaining any user-side storage (please note that if you use this property along ImGuiInputTextFlags_CallbackResize you can end up with your temporary string object unnecessarily allocating once a frame, either store your string data, either if you don't then don't use ImGuiInputTextFlags_CallbackResize). + const bool bApplyEditBackToUserBuffer = !bRevertEdit || (bValidated && (tFlags & PL_UI_INPUT_TEXT_FLAGS_ENTER_RETURNS_TRUE) != 0); + if (bApplyEditBackToUserBuffer) + { + // Apply new value immediately - copy modified buffer back + // Note that as soon as the input box is active, the in-widget value gets priority over any underlying modification of the input buffer + // FIXME: We actually always render 'buf' when calling DrawList->AddText, making the comment above incorrect. + // FIXME-OPT: CPU waste to do this every time the widget is active, should mark dirty state from the stb_textedit callbacks. + + // User callback + // if ((flags & (ImGuiInputTextFlags_CallbackCompletion | ImGuiInputTextFlags_CallbackHistory | ImGuiInputTextFlags_CallbackEdit | ImGuiInputTextFlags_CallbackAlways)) != 0) + // { + // PL_ASSERT(callback != NULL); + + // // The reason we specify the usage semantic (Completion/History) is that Completion needs to disable keyboard TABBING at the moment. + // ImGuiInputTextFlags event_flag = 0; + // ImGuiKey event_key = ImGuiKey_None; + // if ((flags & ImGuiInputTextFlags_CallbackCompletion) != 0 && Shortcut(ImGuiKey_Tab, id)) + // { + // event_flag = ImGuiInputTextFlags_CallbackCompletion; + // event_key = ImGuiKey_Tab; + // } + // else if ((flags & ImGuiInputTextFlags_CallbackHistory) != 0 && IsKeyPressed(ImGuiKey_UpArrow)) + // { + // event_flag = ImGuiInputTextFlags_CallbackHistory; + // event_key = ImGuiKey_UpArrow; + // } + // else if ((flags & ImGuiInputTextFlags_CallbackHistory) != 0 && IsKeyPressed(ImGuiKey_DownArrow)) + // { + // event_flag = ImGuiInputTextFlags_CallbackHistory; + // event_key = ImGuiKey_DownArrow; + // } + // else if ((flags & ImGuiInputTextFlags_CallbackEdit) && state->Edited) + // { + // event_flag = ImGuiInputTextFlags_CallbackEdit; + // } + // else if (flags & ImGuiInputTextFlags_CallbackAlways) + // { + // event_flag = ImGuiInputTextFlags_CallbackAlways; + // } + + // if (event_flag) + // { + // ImGuiInputTextCallbackData callback_data; + // callback_data.Ctx = &g; + // callback_data.EventFlag = event_flag; + // callback_data.Flags = flags; + // callback_data.UserData = callback_user_data; + + // char* callback_buf = bIsReadOnly ? buf : state->TextA.Data; + // callback_data.EventKey = event_key; + // callback_data.Buf = callback_buf; + // callback_data.BufTextLen = ptState->iCurrentLengthA; + // callback_data.BufSize = state->BufCapacityA; + // callback_data.BufDirty = false; + + // // We have to convert from wchar-positions to UTF-8-positions, which can be pretty slow (an incentive to ditch the plUiWChar buffer, see https://github.com/nothings/stb/issues/188) + // plUiWChar* text = ptState->sbTextW; + // const int utf8_cursor_pos = callback_data.CursorPos = pl__text_count_utf8_bytes_from_str(text, text + ptState->tStb.cursor); + // const int utf8_selection_start = callback_data.SelectionStart = pl__text_count_utf8_bytes_from_str(text, text + ptState->tStb.select_start); + // const int utf8_selection_end = callback_data.SelectionEnd = pl__text_count_utf8_bytes_from_str(text, text + ptState->tStb.select_end); + + // // Call user code + // callback(&callback_data); + + // // Read back what user may have modified + // callback_buf = bIsReadOnly ? buf : state->TextA.Data; // Pointer may have been invalidated by a resize callback + // PL_ASSERT(callback_data.Buf == callback_buf); // Invalid to modify those fields + // PL_ASSERT(callback_data.BufSize == state->BufCapacityA); + // PL_ASSERT(callback_data.Flags == flags); + // const bool buf_dirty = callback_data.BufDirty; + // if (callback_data.CursorPos != utf8_cursor_pos || buf_dirty) { ptState->tStb.cursor = pl__text_count_chars_from_utf8(callback_data.Buf, callback_data.Buf + callback_data.CursorPos); ptState->bCursorFollow = true; } + // if (callback_data.SelectionStart != utf8_selection_start || buf_dirty) { ptState->tStb.select_start = (callback_data.SelectionStart == callback_data.CursorPos) ? ptState->tStb.cursor : pl__text_count_chars_from_utf8(callback_data.Buf, callback_data.Buf + callback_data.SelectionStart); } + // if (callback_data.SelectionEnd != utf8_selection_end || buf_dirty) { ptState->tStb.select_end = (callback_data.SelectionEnd == callback_data.SelectionStart) ? ptState->tStb.select_start : pl__text_count_chars_from_utf8(callback_data.Buf, callback_data.Buf + callback_data.SelectionEnd); } + // if (buf_dirty) + // { + // PL_ASSERT((flags & ImGuiInputTextFlags_ReadOnly) == 0); + // PL_ASSERT(callback_data.BufTextLen == (int)strlen(callback_data.Buf)); // You need to maintain BufTextLen if you change the text! + // InputTextReconcileUndoStateAfterUserCallback(state, callback_data.Buf, callback_data.BufTextLen); // FIXME: Move the rest of this block inside function and rename to InputTextReconcileStateAfterUserCallback() ? + // if (callback_data.BufTextLen > backup_current_text_length && is_resizable) + // state->TextW.resize(state->TextW.Size + (callback_data.BufTextLen - backup_current_text_length)); // Worse case scenario resize + // state->CurLenW = pl__text_str_from_utf8(ptState->sbTextW, state->TextW.Size, callback_data.Buf, NULL); + // ptState->iCurrentLengthA = callback_data.BufTextLen; // Assume correct length and valid UTF-8 from user, saves us an extra strlen() + // state->CursorAnimReset(); + // } + // } + // } + + // Will copy result string if modified + if (!bIsReadOnly && strcmp(ptState->sbTextA, pcBuffer) != 0) + { + pcApplyNewText = ptState->sbTextA; + iApplyNewTextLength = ptState->iCurrentLengthA; + bValueChanged = true; + } + } + } + + // Handle reapplying final data on deactivation (see InputTextDeactivateHook() for details) + // if (g.InputTextDeactivatedState.ID == id) + // { + // if (g.ActiveId != id && IsItemDeactivatedAfterEdit() && !is_readonly) + // { + // apply_new_text = g.InputTextDeactivatedState.TextA.Data; + // apply_new_text_length = g.InputTextDeactivatedState.TextA.Size - 1; + // value_changed |= (strcmp(g.InputTextDeactivatedState.TextA.Data, buf) != 0); + // //IMGUI_DEBUG_LOG("InputText(): apply Deactivated data for 0x%08X: \"%.*s\".\n", id, apply_new_text_length, apply_new_text); + // } + // g.InputTextDeactivatedState.ID = 0; + // } + + // Copy result to user buffer. This can currently only happen when (g.ActiveId == id) + if (pcApplyNewText != NULL) + { + // We cannot test for 'backup_current_text_length != apply_new_text_length' here because we have no guarantee that the size + // of our owned buffer matches the size of the string object held by the user, and by design we allow InputText() to be used + // without any storage on user's side. + PL_ASSERT(iApplyNewTextLength >= 0); + // if (bIsResizable) + // { + // ImGuiInputTextCallbackData callback_data; + // callback_data.Ctx = &g; + // callback_data.EventFlag = ImGuiInputTextFlags_CallbackResize; + // callback_data.Flags = flags; + // callback_data.Buf = buf; + // callback_data.BufTextLen = apply_new_text_length; + // callback_data.BufSize = pl_max(buf_size, apply_new_text_length + 1); + // callback_data.UserData = callback_user_data; + // callback(&callback_data); + // buf = callback_data.Buf; + // buf_size = callback_data.BufSize; + // apply_new_text_length = pl_min(callback_data.BufTextLen, buf_size - 1); + // PL_ASSERT(apply_new_text_length <= buf_size); + // } + //IMGUI_DEBUG_PRINT("InputText(\"%s\"): apply_new_text length %d\n", label, apply_new_text_length); + + // If the underlying buffer resize was denied or not carried to the next frame, apply_new_text_length+1 may be >= buf_size. + strncpy(pcBuffer, pcApplyNewText, pl_min(iApplyNewTextLength + 1, szBufferSize)); + } + + // Release active ID at the end of the function (so e.g. pressing Return still does a final application of the value) + // Otherwise request text input ahead for next frame. + if (gptCtx->uActiveId == uHash && bClearActiveId) + pl__set_active_id(0, ptWindow); + else if (gptCtx->uActiveId == uHash) + { + pl__set_active_id(uHash, ptWindow); + gptCtx->tIO.bWantTextInput = true; + } + + // Render frame + if (!bIsMultiLine) + { + // RenderNavHighlight(frame_bb, id); + gptDraw->add_rect_filled(ptWindow->ptFgLayer, tFrameStartPos, tBoundingBox.tMax, gptCtx->tColorScheme.tFrameBgCol); + } + + + // const ImVec4 clip_rect(frame_bb.Min.x, frame_bb.Min.y, frame_bb.Min.x + tInnerSize.x, frame_bb.Min.y + tInnerSize.y); // Not using frame_bb.Max because we have adjusted size + const plRect clip_rect = { + .tMin = tBoundingBox.tMin, + .tMax = { + .x = tBoundingBox.tMin.x + tInnerSize.x, + .y = tBoundingBox.tMin.y + tInnerSize.y + } + }; + plVec2 draw_pos = bIsMultiLine ? tStartPos : pl_add_vec2(tFrameStartPos, gptCtx->tStyle.tFramePadding); + plVec2 text_size = {0}; + + // Set upper limit of single-line InputTextEx() at 2 million characters strings. The current pathological worst case is a long line + // without any carriage return, which would makes ImFont::RenderText() reserve too many vertices and probably crash. Avoid it altogether. + // Note that we only use this limit on single-line InputText(), so a pathologically large line on a InputTextMultiline() would still crash. + const int iBufferDisplayMaxLength = 2 * 1024 * 1024; + const char* pcBufferDisplay = bBufferDisplayFromState ? ptState->sbTextA : pcBuffer; //-V595 + const char* pcBufferDisplayEnd = NULL; // We have specialized paths below for setting the length + if (bIsDisplayingHint) + { + pcBufferDisplay = pcHint; + pcBufferDisplayEnd = pcHint + strlen(pcHint); + } + + // Render text. We currently only render selection when the widget is active or while scrolling. + // FIXME: We could remove the '&& bRenderCursor' to keep rendering selection when inactive. + if (bRenderCursor || bRenderSelection) + { + PL_ASSERT(ptState != NULL); + if (!bIsDisplayingHint) + pcBufferDisplayEnd = pcBufferDisplay + ptState->iCurrentLengthA; + + // Render text (with cursor and selection) + // This is going to be messy. We need to: + // - Display the text (this alone can be more easily clipped) + // - Handle scrolling, highlight selection, display cursor (those all requires some form of 1d->2d cursor position calculation) + // - Measure text height (for scrollbar) + // We are attempting to do most of that in **one main pass** to minimize the computation cost (non-negligible for large amount of text) + 2nd pass for selection rendering (we could merge them by an extra refactoring effort) + // FIXME: This should occur on pcBufferDisplay but we'd need to maintain cursor/select_start/select_end for UTF-8. + const plUiWChar* text_begin = ptState->sbTextW; + plVec2 cursor_offset = {0}; + plVec2 select_start_offset = {0}; + + { + // Find lines numbers straddling 'cursor' (slot 0) and 'select_start' (slot 1) positions. + const plUiWChar* searches_input_ptr[2] = { NULL, NULL }; + int searches_result_line_no[2] = { -1000, -1000 }; + int searches_remaining = 0; + if (bRenderCursor) + { + searches_input_ptr[0] = text_begin + ptState->tStb.cursor; + searches_result_line_no[0] = -1; + searches_remaining++; + } + if (bRenderSelection) + { + searches_input_ptr[1] = text_begin + pl_min(ptState->tStb.select_start, ptState->tStb.select_end); + searches_result_line_no[1] = -1; + searches_remaining++; + } + + // Iterate all lines to find our line numbers + // In multi-line mode, we never exit the loop until all lines are counted, so add one extra to the searches_remaining counter. + searches_remaining += bIsMultiLine ? 1 : 0; + int line_count = 0; + //for (const plUiWChar* s = text_begin; (s = (const plUiWChar*)wcschr((const wchar_t*)s, (wchar_t)'\n')) != NULL; s++) // FIXME-OPT: Could use this when wchar_t are 16-bit + for (const plUiWChar* s = text_begin; *s != 0; s++) + if (*s == '\n') + { + line_count++; + if (searches_result_line_no[0] == -1 && s >= searches_input_ptr[0]) { searches_result_line_no[0] = line_count; if (--searches_remaining <= 0) break; } + if (searches_result_line_no[1] == -1 && s >= searches_input_ptr[1]) { searches_result_line_no[1] = line_count; if (--searches_remaining <= 0) break; } + } + line_count++; + if (searches_result_line_no[0] == -1) + searches_result_line_no[0] = line_count; + if (searches_result_line_no[1] == -1) + searches_result_line_no[1] = line_count; + + // Calculate 2d position by finding the beginning of the line and measuring distance + cursor_offset.x = pl__input_text_calc_text_size_w(pl__str_bol_w(searches_input_ptr[0], text_begin), searches_input_ptr[0], NULL, NULL, false).x; + cursor_offset.y = searches_result_line_no[0] * gptCtx->tStyle.fFontSize; + if (searches_result_line_no[1] >= 0) + { + select_start_offset.x = pl__input_text_calc_text_size_w(pl__str_bol_w(searches_input_ptr[1], text_begin), searches_input_ptr[1], NULL, NULL, false).x; + select_start_offset.y = searches_result_line_no[1] * gptCtx->tStyle.fFontSize; + } + + // Store text height (note that we haven't calculated text width at all, see GitHub issues #383, #1224) + if (bIsMultiLine) + text_size = (plVec2){tInnerSize.x, line_count * gptCtx->tStyle.fFontSize}; + } + + // Scroll + if (bRenderCursor && ptState->bCursorFollow) + { + // Horizontal scroll in chunks of quarter width + if (!(tFlags & PL_UI_INPUT_TEXT_FLAGS_NO_HORIZONTAL_SCROLL)) + { + const float scroll_increment_x = tInnerSize.x * 0.25f; + const float visible_width = tInnerSize.x - gptCtx->tStyle.tFramePadding.x; + if (cursor_offset.x < ptState->fScrollX) + ptState->fScrollX = floorf(pl_max(0.0f, cursor_offset.x - scroll_increment_x)); + else if (cursor_offset.x - visible_width >= ptState->fScrollX) + ptState->fScrollX = floorf(cursor_offset.x - visible_width + scroll_increment_x); + } + else + { + ptState->fScrollX = 0.0f; + } + + // Vertical scroll + if (bIsMultiLine) + { + // Test if cursor is vertically visible + if (cursor_offset.y - gptCtx->tStyle.fFontSize < fScrollY) + fScrollY = pl_max(0.0f, cursor_offset.y - gptCtx->tStyle.fFontSize); + else if (cursor_offset.y - (tInnerSize.y - gptCtx->tStyle.tFramePadding.y * 2.0f) >= fScrollY) + fScrollY = cursor_offset.y - tInnerSize.y + gptCtx->tStyle.tFramePadding.y * 2.0f; + const float scroll_max_y = pl_max((text_size.y + gptCtx->tStyle.tFramePadding.y * 2.0f) - tInnerSize.y, 0.0f); + fScrollY = pl_clampf(0.0f, fScrollY, scroll_max_y); + draw_pos.y += (ptDrawWindow->tScroll.y - fScrollY); // Manipulate cursor pos immediately avoid a frame of lag + ptDrawWindow->tScroll.y = fScrollY; + } + + ptState->bCursorFollow = false; + } + + // Draw selection + const plVec2 draw_scroll = (plVec2){ptState->fScrollX, 0.0f}; + if (bRenderSelection) + { + const plUiWChar* text_selected_begin = text_begin + pl_min(ptState->tStb.select_start, ptState->tStb.select_end); + const plUiWChar* text_selected_end = text_begin + pl_max(ptState->tStb.select_start, ptState->tStb.select_end); + + // ImU32 bg_color = GetColorU32(ImGuiCol_TextSelectedBg, bRenderCursor ? 1.0f : 0.6f); // FIXME: current code flow mandate that bRenderCursor is always true here, we are leaving the transparent one for tests. + float bg_offy_up = bIsMultiLine ? 0.0f : -1.0f; // FIXME: those offsets should be part of the style? they don't play so well with multi-line selection. + float bg_offy_dn = bIsMultiLine ? 0.0f : 2.0f; + plVec2 rect_pos = pl_sub_vec2(pl_add_vec2(draw_pos, select_start_offset), draw_scroll); + for (const plUiWChar* p = text_selected_begin; p < text_selected_end; ) + { + if (rect_pos.y > clip_rect.tMax.y + gptCtx->tStyle.fFontSize) + break; + if (rect_pos.y < clip_rect.tMin.y) + { + //p = (const plUiWChar*)wmemchr((const wchar_t*)p, '\n', text_selected_end - p); // FIXME-OPT: Could use this when wchar_t are 16-bit + //p = p ? p + 1 : text_selected_end; + while (p < text_selected_end) + if (*p++ == '\n') + break; + } + else + { + plVec2 rect_size = pl__input_text_calc_text_size_w(p, text_selected_end, &p, NULL, true); + if (rect_size.x <= 0.0f) rect_size.x = floorf(gptCtx->ptFont->sbtGlyphs[gptCtx->ptFont->sbuCodePoints[(plUiWChar)' ']].xAdvance * 0.50f); // So we can see selected empty lines + plRect rect = { + pl_add_vec2(rect_pos, (plVec2){0.0f, bg_offy_up - gptCtx->tStyle.fFontSize}), + pl_add_vec2(rect_pos, (plVec2){rect_size.x, bg_offy_dn}) + }; + rect = pl_rect_clip(&rect, &clip_rect); + if (pl_rect_overlaps_rect(&rect, &clip_rect)) + gptDraw->add_rect_filled(ptWindow->ptFgLayer, rect.tMin, rect.tMax, (plVec4){1.0f, 0.0f, 0.0f, 1.0f}); + } + rect_pos.x = draw_pos.x - draw_scroll.x; + rect_pos.y += gptCtx->tStyle.fFontSize; + } + } + + // We test for 'buf_display_max_length' as a way to avoid some pathological cases (e.g. single-line 1 MB string) which would make ImDrawList crash. + if (bIsMultiLine || (pcBufferDisplayEnd - pcBufferDisplay) < iBufferDisplayMaxLength) + { + // ImU32 col = GetColorU32(bIsDisplayingHint ? ImGuiCol_TextDisabled : ImGuiCol_Text); + gptDraw->add_text_ex(ptWindow->ptFgLayer, gptCtx->ptFont, gptCtx->tStyle.fFontSize, pl_sub_vec2(draw_pos, draw_scroll), gptCtx->tColorScheme.tTextCol, + pcBufferDisplay, pcBufferDisplayEnd, 0.0f); + // draw_window->DrawList->AddText(g.Font, gptCtx->tStyle.fFontSize, draw_pos - draw_scroll, col, pcBufferDisplay, pcBufferDisplayEnd, 0.0f, bIsMultiLine ? NULL : &clip_rect); + } + + // Draw blinking cursor + if (bRenderCursor) + { + ptState->fCursorAnim += gptCtx->tIO.fDeltaTime; + // bool cursor_is_visible = (!g.IO.ConfigInputTextCursorBlink) || (ptState->fCursorAnim <= 0.0f) || fmodf(ptState->fCursorAnim, 1.20f) <= 0.80f; + bool bCursorIsVisible = (ptState->fCursorAnim <= 0.0f) || fmodf(ptState->fCursorAnim, 1.20f) <= 0.80f; + plVec2 cursor_screen_pos = pl_floor_vec2(pl_sub_vec2(pl_add_vec2(draw_pos, cursor_offset), draw_scroll)); + plRect cursor_screen_rect = { + {cursor_screen_pos.x, cursor_screen_pos.y - gptCtx->tStyle.fFontSize + 0.5f}, + {cursor_screen_pos.x + 1.0f, cursor_screen_pos.y - 1.5f} + }; + if (bCursorIsVisible && pl_rect_overlaps_rect(&cursor_screen_rect, &clip_rect)) + gptDraw->add_line(ptWindow->ptFgLayer, cursor_screen_rect.tMin, pl_rect_bottom_left(&cursor_screen_rect), gptCtx->tColorScheme.tTextCol, 1.0f); + + // Notify OS of text input position for advanced IME (-1 x offset so that Windows IME can cover our cursor. Bit of an extra nicety.) + // if (!bIsReadOnly) + // { + // g.PlatformImeData.WantVisible = true; + // g.PlatformImeData.InputPos = plVec2(cursor_screen_pos.x - 1.0f, cursor_screen_pos.y - gptCtx->tStyle.fFontSize); + // g.PlatformImeData.InputLineHeight = gptCtx->tStyle.fFontSize; + // } + } + } + else + { + // Render text only (no selection, no cursor) + if (bIsMultiLine) + text_size = (plVec2){tInnerSize.x, pl__input_text_calc_text_len_and_line_count(pcBufferDisplay, &pcBufferDisplayEnd) * gptCtx->tStyle.fFontSize}; // We don't need width + else if (!bIsDisplayingHint && gptCtx->uActiveId == uHash) + pcBufferDisplayEnd = pcBufferDisplay + ptState->iCurrentLengthA; + else if (!bIsDisplayingHint) + pcBufferDisplayEnd = pcBufferDisplay + strlen(pcBufferDisplay); + + if (bIsMultiLine || (pcBufferDisplayEnd - pcBufferDisplay) < iBufferDisplayMaxLength) + { + gptDraw->add_text_ex(ptWindow->ptFgLayer, gptCtx->ptFont, gptCtx->tStyle.fFontSize, draw_pos, gptCtx->tColorScheme.tTextCol, + pcBufferDisplay, pcBufferDisplayEnd, 0.0f); + } + } + + // if (bIsPassword && !bIsDisplayingHint) + // PopFont(); + + if (bIsMultiLine) + { + // // For focus requests to work on our multiline we need to ensure our child ItemAdd() call specifies the ImGuiItemFlags_Inputable (ref issue #4761)... + // Dummy(ImVec2(text_size.x, text_size.y + style.FramePadding.y)); + // ImGuiItemFlags backup_item_flags = g.CurrentItemFlags; + // g.CurrentItemFlags |= ImGuiItemFlags_Inputable | ImGuiItemFlags_NoTabStop; + // EndChild(); + // item_data_backup.StatusFlags |= (g.LastItemData.StatusFlags & ImGuiItemStatusFlags_HoveredWindow); + // g.CurrentItemFlags = backup_item_flags; + + // // ...and then we need to undo the group overriding last item data, which gets a bit messy as EndGroup() tries to forward scrollbar being active... + // // FIXME: This quite messy/tricky, should attempt to get rid of the child window. + // EndGroup(); + // if (g.LastItemData.ID == 0) + // { + // g.LastItemData.ID = id; + // g.LastItemData.InFlags = item_data_backup.InFlags; + // g.LastItemData.StatusFlags = item_data_backup.StatusFlags; + // } + } + + // if (pcLabel.x > 0) + { + // RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label); + pl_ui_add_text(ptWindow->ptFgLayer, gptCtx->ptFont, gptCtx->tStyle.fFontSize, (plVec2){tStartPos.x, tStartPos.y + tStartPos.y + tWidgetSize.y / 2.0f - tLabelTextActualCenter.y}, gptCtx->tColorScheme.tTextCol, pcLabel, -1.0f); + } + + // if (value_changed && !(flags & ImGuiInputTextFlags_NoMarkEdited)) + // MarkItemEdited(id); + + pl_advance_cursor(tWidgetSize.x, tWidgetSize.y); + + if ((tFlags & PL_UI_INPUT_TEXT_FLAGS_ENTER_RETURNS_TRUE) != 0) + return bValidated; + else + return bValueChanged; +} + +bool +pl_slider_float_f(const char* pcLabel, float* pfValue, float fMin, float fMax, const char* pcFormat) +{ + plUiWindow* ptWindow = gptCtx->ptCurrentWindow; + const plVec2 tWidgetSize = pl_calculate_item_size(pl_get_frame_height()); + const plVec2 tStartPos = pl__ui_get_cursor_pos(); + + const float fOriginalValue = *pfValue; + if(pl__ui_should_render(&tStartPos, &tWidgetSize)) + { + const plVec2 tFrameStartPos = {floorf(tStartPos.x + (tWidgetSize.x / 3.0f)), tStartPos.y }; + *pfValue = pl_clampf(fMin, *pfValue, fMax); + const uint32_t uHash = pl_str_hash(pcLabel, 0, pl_sb_top(gptCtx->sbuIdStack)); + + char acTextBuffer[64] = {0}; + pl_sprintf(acTextBuffer, pcFormat, *pfValue); + const plRect tTextBounding = gptDraw->calculate_text_bb_ex(gptCtx->ptFont, gptCtx->tStyle.fFontSize, tFrameStartPos, acTextBuffer, pl_find_renderered_text_end(acTextBuffer, NULL), -1.0f); + const plRect tLabelTextBounding = gptDraw->calculate_text_bb_ex(gptCtx->ptFont, gptCtx->tStyle.fFontSize, tFrameStartPos, pcLabel, pl_find_renderered_text_end(pcLabel, NULL), -1.0f); + const plVec2 tTextActualCenter = pl_rect_center(&tTextBounding); + const plVec2 tLabelTextActualCenter = pl_rect_center(&tLabelTextBounding); + + const plVec2 tSize = { 2.0f * (tWidgetSize.x / 3.0f), tWidgetSize.y}; + const plVec2 tTextStartPos = { + tFrameStartPos.x + tFrameStartPos.x + (2.0f * (tWidgetSize.x / 3.0f)) / 2.0f - tTextActualCenter.x, + tFrameStartPos.y + tFrameStartPos.y + tWidgetSize.y / 2.0f - tTextActualCenter.y + }; + + const float fRange = fMax - fMin; + const float fConv = fRange / (tSize.x - gptCtx->tStyle.fSliderSize); + + const plVec2 tGrabStartPos = { + .x = tFrameStartPos.x + ((*pfValue) - fMin) / fConv, + .y = tFrameStartPos.y + }; + + const plVec2 tGrabSize = { gptCtx->tStyle.fSliderSize, tWidgetSize.y}; + plRect tGrabBox = pl_calculate_rect(tGrabStartPos, tGrabSize); + const plRect* ptClipRect = gptDraw->get_clip_rect(gptCtx->ptDrawlist); + tGrabBox = pl_rect_clip_full(&tGrabBox, ptClipRect); + bool bHovered = false; + bool bHeld = false; + const bool bPressed = pl_button_behavior(&tGrabBox, uHash, &bHovered, &bHeld); + + const plRect tBoundingBox = pl_calculate_rect(tFrameStartPos, tSize); + if(gptCtx->uActiveId == uHash) gptDraw->add_rect_filled(ptWindow->ptFgLayer, tFrameStartPos, tBoundingBox.tMax, gptCtx->tColorScheme.tFrameBgActiveCol); + else if(gptCtx->uHoveredId == uHash) gptDraw->add_rect_filled(ptWindow->ptFgLayer, tFrameStartPos, tBoundingBox.tMax, gptCtx->tColorScheme.tFrameBgHoveredCol); + else gptDraw->add_rect_filled(ptWindow->ptFgLayer, tFrameStartPos, tBoundingBox.tMax, gptCtx->tColorScheme.tFrameBgCol); + + gptDraw->add_rect_filled(ptWindow->ptFgLayer, tGrabStartPos, tGrabBox.tMax, gptCtx->tColorScheme.tButtonCol); + pl_ui_add_text(ptWindow->ptFgLayer, gptCtx->ptFont, gptCtx->tStyle.fFontSize, (plVec2){tStartPos.x, tStartPos.y + tStartPos.y + tWidgetSize.y / 2.0f - tLabelTextActualCenter.y}, gptCtx->tColorScheme.tTextCol, pcLabel, -1.0f); + pl_ui_add_text(ptWindow->ptFgLayer, gptCtx->ptFont, gptCtx->tStyle.fFontSize, tTextStartPos, gptCtx->tColorScheme.tTextCol, acTextBuffer, -1.0f); + + bool bDragged = false; + if(gptCtx->uActiveId == uHash && gptIOI->is_mouse_dragging(PL_MOUSE_BUTTON_LEFT, 1.0f)) + { + *pfValue += gptIOI->get_mouse_drag_delta(PL_MOUSE_BUTTON_LEFT, 1.0f).x * fConv; + *pfValue = pl_clampf(fMin, *pfValue, fMax); + if(gptIOI->get_mouse_pos().x < tBoundingBox.tMin.x) *pfValue = fMin; + if(gptIOI->get_mouse_pos().x > tBoundingBox.tMax.x) *pfValue = fMax; + gptIOI->reset_mouse_drag_delta(PL_MOUSE_BUTTON_LEFT); + pl__set_active_id(uHash, ptWindow); + } + } + pl_advance_cursor(tWidgetSize.x, tWidgetSize.y); + return fOriginalValue != *pfValue; +} + +bool +pl_slider_float(const char* pcLabel, float* pfValue, float fMin, float fMax) +{ + return pl_slider_float_f(pcLabel, pfValue, fMin, fMax, "%0.3f"); +} + +bool +pl_slider_int_f(const char* pcLabel, int* piValue, int iMin, int iMax, const char* pcFormat) +{ + plUiWindow* ptWindow = gptCtx->ptCurrentWindow; + const plVec2 tWidgetSize = pl_calculate_item_size(pl_get_frame_height()); + const plVec2 tStartPos = pl__ui_get_cursor_pos(); + + const int iOriginalValue = *piValue; + if(pl__ui_should_render(&tStartPos, &tWidgetSize)) + { + const plVec2 tFrameStartPos = {floorf(tStartPos.x + (tWidgetSize.x / 3.0f)), tStartPos.y }; + + *piValue = pl_clampi(iMin, *piValue, iMax); + const uint32_t uHash = pl_str_hash(pcLabel, 0, pl_sb_top(gptCtx->sbuIdStack)); + const int iBlocks = iMax - iMin + 1; + const int iBlock = *piValue - iMin; + + char acTextBuffer[64] = {0}; + pl_sprintf(acTextBuffer, pcFormat, *piValue); + const plRect tTextBounding = gptDraw->calculate_text_bb_ex(gptCtx->ptFont, gptCtx->tStyle.fFontSize, tFrameStartPos, acTextBuffer, pl_find_renderered_text_end(acTextBuffer, NULL), -1.0f); + const plRect tLabelTextBounding = gptDraw->calculate_text_bb_ex(gptCtx->ptFont, gptCtx->tStyle.fFontSize, tFrameStartPos, pcLabel, pl_find_renderered_text_end(pcLabel, NULL), -1.0f); + const plVec2 tTextActualCenter = pl_rect_center(&tTextBounding); + const plVec2 tLabelTextActualCenter = pl_rect_center(&tLabelTextBounding); + + const plVec2 tSize = { 2.0f * (tWidgetSize.x / 3.0f), tWidgetSize.y}; + const plVec2 tTextStartPos = { + tFrameStartPos.x + tFrameStartPos.x + (2.0f * (tWidgetSize.x / 3.0f)) / 2.0f - tTextActualCenter.x, + tFrameStartPos.y + tFrameStartPos.y + tWidgetSize.y / 2.0f - tTextActualCenter.y + }; + const float fBlockLength = tSize.x / (float)iBlocks; + + const plVec2 tGrabStartPos = { + .x = tFrameStartPos.x + (float)iBlock * fBlockLength, + .y = tFrameStartPos.y + }; + + const plVec2 tGrabSize = { fBlockLength, tWidgetSize.y}; + plRect tGrabBox = pl_calculate_rect(tGrabStartPos, tGrabSize); + const plRect* ptClipRect = gptDraw->get_clip_rect(gptCtx->ptDrawlist); + tGrabBox = pl_rect_clip_full(&tGrabBox, ptClipRect); + bool bHovered = false; + bool bHeld = false; + const bool bPressed = pl_button_behavior(&tGrabBox, uHash, &bHovered, &bHeld); + + const plRect tBoundingBox = pl_calculate_rect(tFrameStartPos, tSize); + if(gptCtx->uActiveId == uHash) gptDraw->add_rect_filled(ptWindow->ptFgLayer, tFrameStartPos, tBoundingBox.tMax, gptCtx->tColorScheme.tFrameBgActiveCol); + else if(gptCtx->uHoveredId == uHash) gptDraw->add_rect_filled(ptWindow->ptFgLayer, tFrameStartPos, tBoundingBox.tMax, gptCtx->tColorScheme.tFrameBgHoveredCol); + else gptDraw->add_rect_filled(ptWindow->ptFgLayer, tFrameStartPos, tBoundingBox.tMax, gptCtx->tColorScheme.tFrameBgCol); + + gptDraw->add_rect_filled(ptWindow->ptFgLayer, tGrabStartPos, tGrabBox.tMax, gptCtx->tColorScheme.tButtonCol); + pl_ui_add_text(ptWindow->ptFgLayer, gptCtx->ptFont, gptCtx->tStyle.fFontSize, (plVec2){tStartPos.x, tStartPos.y + tStartPos.y + tWidgetSize.y / 2.0f - tLabelTextActualCenter.y}, gptCtx->tColorScheme.tTextCol, pcLabel, -1.0f); + pl_ui_add_text(ptWindow->ptFgLayer, gptCtx->ptFont, gptCtx->tStyle.fFontSize, tTextStartPos, gptCtx->tColorScheme.tTextCol, acTextBuffer, -1.0f); + + bool bDragged = false; + if(gptCtx->uActiveId == uHash && gptIOI->is_mouse_dragging(PL_MOUSE_BUTTON_LEFT, 1.0f)) + { + const plVec2 tMousePos = gptIOI->get_mouse_pos(); + + if(tMousePos.x > tGrabBox.tMax.x) + (*piValue)++; + if(tMousePos.x < tGrabStartPos.x) + (*piValue)--; + + *piValue = pl_clampi(iMin, *piValue, iMax); + pl__set_active_id(uHash, ptWindow); + } + } + pl_advance_cursor(tWidgetSize.x, tWidgetSize.y); + return iOriginalValue != *piValue; +} + +bool +pl_slider_int(const char* pcLabel, int* piValue, int iMin, int iMax) +{ + return pl_slider_int_f(pcLabel, piValue, iMin, iMax, "%d"); +} + +bool +pl_drag_float_f(const char* pcLabel, float* pfValue, float fSpeed, float fMin, float fMax, const char* pcFormat) +{ + plUiWindow* ptWindow = gptCtx->ptCurrentWindow; + plUiLayoutRow* ptCurrentRow = &ptWindow->tTempData.tCurrentLayoutRow; + const plVec2 tWidgetSize = pl_calculate_item_size(pl_get_frame_height()); + const plVec2 tStartPos = pl__ui_get_cursor_pos(); + const float fOriginalValue = *pfValue; + if(pl__ui_should_render(&tStartPos, &tWidgetSize)) + { + const plVec2 tFrameStartPos = {floorf(tStartPos.x + (tWidgetSize.x / 3.0f)), tStartPos.y }; + + *pfValue = pl_clampf(fMin, *pfValue, fMax); + const uint32_t uHash = pl_str_hash(pcLabel, 0, pl_sb_top(gptCtx->sbuIdStack)); + + char acTextBuffer[64] = {0}; + pl_sprintf(acTextBuffer, pcFormat, *pfValue); + const plRect tTextBounding = gptDraw->calculate_text_bb_ex(gptCtx->ptFont, gptCtx->tStyle.fFontSize, tFrameStartPos, acTextBuffer, pl_find_renderered_text_end(acTextBuffer, NULL), -1.0f); + const plRect tLabelTextBounding = gptDraw->calculate_text_bb_ex(gptCtx->ptFont, gptCtx->tStyle.fFontSize, tFrameStartPos, pcLabel, pl_find_renderered_text_end(pcLabel, NULL), -1.0f); + const plVec2 tTextActualCenter = pl_rect_center(&tTextBounding); + const plVec2 tLabelTextActualCenter = pl_rect_center(&tLabelTextBounding); + + const plVec2 tSize = { 2.0f * (tWidgetSize.x / 3.0f), tWidgetSize.y}; + const plVec2 tTextStartPos = { + tFrameStartPos.x + tFrameStartPos.x + (2.0f * (tWidgetSize.x / 3.0f)) / 2.0f - tTextActualCenter.x, + tFrameStartPos.y + tFrameStartPos.y + tWidgetSize.y / 2.0f - tTextActualCenter.y + }; + plRect tBoundingBox = pl_calculate_rect(tFrameStartPos, tSize); + const plRect* ptClipRect = gptDraw->get_clip_rect(gptCtx->ptDrawlist); + tBoundingBox = pl_rect_clip_full(&tBoundingBox, ptClipRect); + + bool bHovered = false; + bool bHeld = false; + const bool bPressed = pl_button_behavior(&tBoundingBox, uHash, &bHovered, &bHeld); + + if(gptCtx->uActiveId == uHash) gptDraw->add_rect_filled(ptWindow->ptFgLayer, tFrameStartPos, tBoundingBox.tMax, gptCtx->tColorScheme.tFrameBgActiveCol); + else if(gptCtx->uHoveredId == uHash) gptDraw->add_rect_filled(ptWindow->ptFgLayer, tFrameStartPos, tBoundingBox.tMax, gptCtx->tColorScheme.tFrameBgHoveredCol); + else gptDraw->add_rect_filled(ptWindow->ptFgLayer, tFrameStartPos, tBoundingBox.tMax, gptCtx->tColorScheme.tFrameBgCol); + + pl_ui_add_text(ptWindow->ptFgLayer, gptCtx->ptFont, gptCtx->tStyle.fFontSize, (plVec2){tStartPos.x, tStartPos.y + tStartPos.y + tWidgetSize.y / 2.0f - tLabelTextActualCenter.y}, gptCtx->tColorScheme.tTextCol, pcLabel, -1.0f); + pl_ui_add_text(ptWindow->ptFgLayer, gptCtx->ptFont, gptCtx->tStyle.fFontSize, tTextStartPos, gptCtx->tColorScheme.tTextCol, acTextBuffer, -1.0f); + + bool bDragged = false; + if(gptCtx->uActiveId == uHash && gptIOI->is_mouse_dragging(PL_MOUSE_BUTTON_LEFT, 1.0f)) + { + *pfValue = gptIOI->get_mouse_drag_delta(PL_MOUSE_BUTTON_LEFT, 1.0f).x * fSpeed; + *pfValue = pl_clampf(fMin, *pfValue, fMax); + pl__set_active_id(uHash, ptWindow); + } + } + pl_advance_cursor(tWidgetSize.x, tWidgetSize.y); + return fOriginalValue != *pfValue; +} + +bool +pl_drag_float(const char* pcLabel, float* pfValue, float fSpeed, float fMin, float fMax) +{ + return pl_drag_float_f(pcLabel, pfValue, fSpeed, fMin, fMax, "%.3f"); +} + +void +pl_image_ex(plTextureHandle tTexture, plVec2 tSize, plVec2 tUv0, plVec2 tUv1, plVec4 tTintColor, plVec4 tBorderColor) +{ + plUiWindow* ptWindow = gptCtx->ptCurrentWindow; + const plVec2 tStartPos = pl__ui_get_cursor_pos(); + + const plVec2 tFinalPos = pl_add_vec2(tStartPos, tSize); + + if(!(tFinalPos.y < ptWindow->tPos.y || tStartPos.y > ptWindow->tPos.y + ptWindow->tFullSize.y)) + { + + gptDraw->add_image_ex(ptWindow->ptFgLayer, tTexture, tStartPos, tFinalPos, tUv0, tUv1, tTintColor); + + if(tBorderColor.a > 0.0f) + gptDraw->add_rect(ptWindow->ptFgLayer, tStartPos, tFinalPos, tBorderColor, 1.0f); + + } + pl_advance_cursor(tSize.x, tSize.y); +} + +void +pl_image(plTextureHandle tTexture, plVec2 tSize) +{ + pl_image_ex(tTexture, tSize, (plVec2){0}, (plVec2){1.0f, 1.0f}, (plVec4){1.0f, 1.0f, 1.0f, 1.0f}, (plVec4){0}); +} + +bool +pl_image_button_ex(const char* pcId, plTextureHandle tTexture, plVec2 tSize, plVec2 tUv0, plVec2 tUv1, plVec4 tTintColor, plVec4 tBorderColor) +{ + plUiWindow* ptWindow = gptCtx->ptCurrentWindow; + const plVec2 tStartPos = pl__ui_get_cursor_pos(); + + const plVec2 tFinalPos = pl_add_vec2(tStartPos, tSize); + + bool bPressed = false; + if(!(tFinalPos.y < ptWindow->tPos.y || tStartPos.y > ptWindow->tPos.y + ptWindow->tFullSize.y)) + { + + const uint32_t uHash = pl_str_hash(pcId, 0, pl_sb_top(gptCtx->sbuIdStack)); + plRect tBoundingBox = pl_calculate_rect(tStartPos, tSize); + const plRect* ptClipRect = gptDraw->get_clip_rect(gptCtx->ptDrawlist); + tBoundingBox = pl_rect_clip_full(&tBoundingBox, ptClipRect); + + bool bHovered = false; + bool bHeld = false; + bPressed = pl_button_behavior(&tBoundingBox, uHash, &bHovered, &bHeld); + + gptDraw->add_image_ex(ptWindow->ptFgLayer, tTexture, tStartPos, tFinalPos, tUv0, tUv1, tTintColor); + + if(tBorderColor.a > 0.0f) + gptDraw->add_rect(ptWindow->ptFgLayer, tStartPos, tFinalPos, tBorderColor, 1.0f); + + } + pl_advance_cursor(tSize.x, tSize.y); + return bPressed; +} + +bool +pl_image_button(const char* pcId, plTextureHandle tTexture, plVec2 tSize) +{ + return pl_image_button_ex(pcId, tTexture, tSize, (plVec2){0}, (plVec2){1.0f, 1.0f}, (plVec4){1.0f, 1.0f, 1.0f, 1.0f}, (plVec4){0}); +} + +bool +pl_invisible_button(const char* pcText, plVec2 tSize) +{ + plUiWindow* ptWindow = gptCtx->ptCurrentWindow; + plUiLayoutRow* ptCurrentRow = &ptWindow->tTempData.tCurrentLayoutRow; + const plVec2 tStartPos = pl__ui_get_cursor_pos(); + + bool bPressed = false; + if(!(tStartPos.y + tSize.y < ptWindow->tPos.y || tStartPos.y > ptWindow->tPos.y + ptWindow->tFullSize.y)) + { + const uint32_t uHash = pl_str_hash(pcText, 0, pl_sb_top(gptCtx->sbuIdStack)); + plRect tBoundingBox = pl_calculate_rect(tStartPos, tSize); + const plRect* ptClipRect = gptDraw->get_clip_rect(gptCtx->ptDrawlist); + tBoundingBox = pl_rect_clip_full(&tBoundingBox, ptClipRect); + + bool bHovered = false; + bool bHeld = false; + bPressed = pl_button_behavior(&tBoundingBox, uHash, &bHovered, &bHeld); + } + pl_advance_cursor(tSize.x, tSize.y); + return bPressed; +} + +void +pl_dummy(plVec2 tSize) +{ + plUiWindow* ptWindow = gptCtx->ptCurrentWindow; + plUiLayoutRow* ptCurrentRow = &ptWindow->tTempData.tCurrentLayoutRow; + pl_advance_cursor(tSize.x, tSize.y); +} + +void +pl_progress_bar(float fFraction, plVec2 tSize, const char* pcOverlay) +{ + plUiWindow* ptWindow = gptCtx->ptCurrentWindow; + plUiLayoutRow* ptCurrentRow = &ptWindow->tTempData.tCurrentLayoutRow; + const plVec2 tWidgetSize = pl_calculate_item_size(pl_get_frame_height()); + const plVec2 tStartPos = pl__ui_get_cursor_pos(); + + if(tSize.y == 0.0f) tSize.y = tWidgetSize.y; + if(tSize.x < 0.0f) tSize.x = tWidgetSize.x; + + if(!(tStartPos.y + tSize.y < ptWindow->tPos.y || tStartPos.y > ptWindow->tPos.y + ptWindow->tFullSize.y)) + { + + gptDraw->add_rect_filled(ptWindow->ptFgLayer, tStartPos, pl_add_vec2(tStartPos, tSize), gptCtx->tColorScheme.tFrameBgCol); + gptDraw->add_rect_filled(ptWindow->ptFgLayer, tStartPos, pl_add_vec2(tStartPos, (plVec2){tSize.x * fFraction, tSize.y}), gptCtx->tColorScheme.tProgressBarCol); + + const char* pcTextPtr = pcOverlay; + + if(pcOverlay == NULL) + { + static char acBuffer[32] = {0}; + pl_sprintf(acBuffer, "%.1f%%", 100.0f * fFraction); + pcTextPtr = acBuffer; + } + + const plVec2 tTextSize = pl_ui_calculate_text_size(gptCtx->ptFont, gptCtx->tStyle.fFontSize, pcTextPtr, -1.0f); + plRect tTextBounding = gptDraw->calculate_text_bb_ex(gptCtx->ptFont, gptCtx->tStyle.fFontSize, tStartPos, pcTextPtr, pl_find_renderered_text_end(pcTextPtr, NULL), -1.0f); + const plVec2 tTextActualCenter = pl_rect_center(&tTextBounding); + + plVec2 tTextStartPos = { + .x = tStartPos.x + gptCtx->tStyle.tInnerSpacing.x + gptCtx->tStyle.tFramePadding.x + tSize.x * fFraction, + .y = tStartPos.y + tStartPos.y + tWidgetSize.y / 2.0f - tTextActualCenter.y + }; + + if(tTextStartPos.x + tTextSize.x > tStartPos.x + tSize.x) + tTextStartPos.x = tStartPos.x + tSize.x - tTextSize.x - gptCtx->tStyle.tInnerSpacing.x; + + pl_ui_add_text(ptWindow->ptFgLayer, gptCtx->ptFont, gptCtx->tStyle.fFontSize, tTextStartPos, gptCtx->tColorScheme.tTextCol, pcTextPtr, -1.0f); + + const bool bHovered = gptIOI->is_mouse_hovering_rect(tStartPos, pl_add_vec2(tStartPos, tWidgetSize)) && ptWindow == gptCtx->ptHoveredWindow; + gptCtx->tPrevItemData.bHovered = bHovered; + } + pl_advance_cursor(tWidgetSize.x, tWidgetSize.y); +} + +//----------------------------------------------------------------------------- +// [SECTION] internal api implementation +//----------------------------------------------------------------------------- + +static bool +pl__input_text_filter_character(unsigned int* puChar, plUiInputTextFlags tFlags) +{ + //PL_ASSERT(input_source == ImGuiInputSource_Keyboard || input_source == ImGuiInputSource_Clipboard); + unsigned int c = *puChar; + + // Filter non-printable (NB: isprint is unreliable! see #2467) + bool apply_named_filters = true; + if (c < 0x20) + { + bool pass = false; + pass |= (c == '\n' && (tFlags & PL_UI_INPUT_TEXT_FLAGS_MULTILINE)); // Note that an Enter KEY will emit \r and be ignored (we poll for KEY in InputText() code) + pass |= (c == '\t' && (tFlags & PL_UI_INPUT_TEXT_FLAGS_ALLOW_TAB_INPUT)); + if (!pass) + return false; + apply_named_filters = false; // Override named filters below so newline and tabs can still be inserted. + } + + // if (input_source != ImGuiInputSource_Clipboard) + { + // We ignore Ascii representation of delete (emitted from Backspace on OSX, see #2578, #2817) + if (c == 127) + return false; + + // Filter private Unicode range. GLFW on OSX seems to send private characters for special keys like arrow keys (FIXME) + if (c >= 0xE000 && c <= 0xF8FF) + return false; + } + + // Filter Unicode ranges we are not handling in this build + if (c > PL_UNICODE_CODEPOINT_MAX) + return false; + + // Generic named filters + if (apply_named_filters && (tFlags & (PL_UI_INPUT_TEXT_FLAGS_CHARS_DECIMAL | PL_UI_INPUT_TEXT_FLAGS_CHARS_HEXADECIMAL | PL_UI_INPUT_TEXT_FLAGS_CHARS_UPPERCASE | PL_UI_INPUT_TEXT_FLAGS_CHARS_NO_BLANK | PL_UI_INPUT_TEXT_FLAGS_CHARS_SCIENTIFIC))) + { + // The libc allows overriding locale, with e.g. 'setlocale(LC_NUMERIC, "de_DE.UTF-8");' which affect the output/input of printf/scanf to use e.g. ',' instead of '.'. + // The standard mandate that programs starts in the "C" locale where the decimal point is '.'. + // We don't really intend to provide widespread support for it, but out of empathy for people stuck with using odd API, we support the bare minimum aka overriding the decimal point. + // Change the default decimal_point with: + // ImGui::GetCurrentContext()->PlatformLocaleDecimalPoint = *localeconv()->decimal_point; + // Users of non-default decimal point (in particular ',') may be affected by word-selection logic (pl__is_word_boundary_from_right/pl__is_word_boundary_from_left) functions. + const unsigned c_decimal_point = '.'; + + // Full-width -> half-width conversion for numeric fields (https://en.wikipedia.org/wiki/Halfwidth_and_Fullwidth_Forms_(Unicode_block) + // While this is mostly convenient, this has the side-effect for uninformed users accidentally inputting full-width characters that they may + // scratch their head as to why it works in numerical fields vs in generic text fields it would require support in the font. + if (tFlags & (PL_UI_INPUT_TEXT_FLAGS_CHARS_DECIMAL | PL_UI_INPUT_TEXT_FLAGS_CHARS_SCIENTIFIC | PL_UI_INPUT_TEXT_FLAGS_CHARS_HEXADECIMAL)) + if (c >= 0xFF01 && c <= 0xFF5E) + c = c - 0xFF01 + 0x21; + + // Allow 0-9 . - + * / + if (tFlags & PL_UI_INPUT_TEXT_FLAGS_CHARS_DECIMAL) + if (!(c >= '0' && c <= '9') && (c != c_decimal_point) && (c != '-') && (c != '+') && (c != '*') && (c != '/')) + return false; + + // Allow 0-9 . - + * / e E + if (tFlags & PL_UI_INPUT_TEXT_FLAGS_CHARS_SCIENTIFIC) + if (!(c >= '0' && c <= '9') && (c != c_decimal_point) && (c != '-') && (c != '+') && (c != '*') && (c != '/') && (c != 'e') && (c != 'E')) + return false; + + // Allow 0-9 a-F A-F + if (tFlags & PL_UI_INPUT_TEXT_FLAGS_CHARS_HEXADECIMAL) + if (!(c >= '0' && c <= '9') && !(c >= 'a' && c <= 'f') && !(c >= 'A' && c <= 'F')) + return false; + + // Turn a-z into A-Z + if (tFlags & PL_UI_INPUT_TEXT_FLAGS_CHARS_UPPERCASE) + if (c >= 'a' && c <= 'z') + c += (unsigned int)('A' - 'a'); + + if (tFlags & PL_UI_INPUT_TEXT_FLAGS_CHARS_NO_BLANK) + if (pl__char_is_blank_w(c)) + return false; + + *puChar = c; + } + + // Custom callback filter + if (tFlags & PL_UI_INPUT_TEXT_FLAGS_CALLBACK_CHAR_FILTER) + { + // ImGuiContext& g = *GImGui; + // ImGuiInputTextCallbackData callback_data; + // callback_data.Ctx = &g; + // callback_data.EventFlag = ImGuiInputTextFlags_CallbackCharFilter; + // callback_data.EventChar = (ImWchar)c; + // callback_data.Flags = tFlags; + // callback_data.UserData = user_data; + // if (callback(&callback_data) != 0) + // return false; + // *p_char = callback_data.EventChar; + // if (!callback_data.EventChar) + // return false; + } + + return true; +} \ No newline at end of file