From 96c01ca7377cb9261ebf4af25b6defcf3ee41975 Mon Sep 17 00:00:00 2001 From: Jonathan Hoffstadt Date: Wed, 12 Feb 2025 16:03:57 -0600 Subject: [PATCH] initial commit --- .gitignore | 16 + README.md | 84 ++++++ extensions/pl_example_ext.c | 52 ++++ extensions/pl_example_ext.h | 35 +++ scripts/gen_build.py | 198 +++++++++++++ scripts/setup.py | 135 +++++++++ src/app.c | 569 ++++++++++++++++++++++++++++++++++++ 7 files changed, 1089 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 extensions/pl_example_ext.c create mode 100644 extensions/pl_example_ext.h create mode 100644 scripts/gen_build.py create mode 100644 scripts/setup.py create mode 100644 src/app.c diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6426e71 --- /dev/null +++ b/.gitignore @@ -0,0 +1,16 @@ + +# directories +.vs/ +.vscode/ +.idea/ + +# python +scripts/__pycache__/ + +# random files +src/*.pdb +src/*.bat +src/*.sh + +# misc +.DS_Store \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..9e99c72 --- /dev/null +++ b/README.md @@ -0,0 +1,84 @@ + +# Pilot Light Template + + +## Information +This repository acts as a template for creating a [Pilot Light](https://github.com/PilotLightTech/pilotlight) application using the engine source. In the future, a +binary template will be created. + +## Build + +As a first step, clone and build **Pilot Light**. Here we are assuming it is adjacent to this repo. If not, pass the +correct relative location to **gen_build.py**. + +### Windows + +From within a local directory, enter the following commands in your terminal: +```bash +# clone & build pilot light +git clone https://github.com/PilotLightTech/pilotlight +cd pilotlight/src +build_win32.bat + +# clone & build example +cd .. +git clone https://git.pilotlight.tech/pilotlight/pilotlight-template +cd pilotlight-template/scripts +python setup.py +python gen_build.py ../../pilotlight +cd ../src +build.bat +``` + +### Linux + +From within a local directory, enter the following commands in your terminal: +```bash +# clone & build pilot light +git clone https://github.com/PilotLightTech/pilotlight +cd pilotlight/src +chmod +x build_linux.sh +./build_linux.sh + +# clone & build example +cd .. +git clone https://git.pilotlight.tech/pilotlight/pilotlight-template +cd pilotlight-template/scripts +python3 setup.py +python3 gen_build.py ../../pilotlight +cd ../src +chmod +x build.sh +./build.sh +``` + +### MacOS + +From within a local directory, enter the following commands in your terminal: +```bash +# clone & build pilot light +git clone https://github.com/PilotLightTech/pilotlight +cd pilotlight/src +chmod +x build_linux.sh +./build_linux.sh + +# clone & build example +cd .. +git clone https://git.pilotlight.tech/pilotlight/pilotlight-template +cd pilotlight-template/scripts +python3 setup.py +python3 gen_build.py ../../pilotlight +cd ../src +chmod +x build.sh +./build.sh +``` + +Binaries will be in _pilotlight/out/_. + +Run the application by pressing F5 if using VSCode or manually like so: +```bash +# windows +pilot_light.exe -a template_app + +# linux/macos +./pilot_light -a template_app +``` \ No newline at end of file diff --git a/extensions/pl_example_ext.c b/extensions/pl_example_ext.c new file mode 100644 index 0000000..939f834 --- /dev/null +++ b/extensions/pl_example_ext.c @@ -0,0 +1,52 @@ +/* + pl_example_ext.c +*/ + +/* +Index of this file: +// [SECTION] includes +// [SECTION] public api implementation +// [SECTION] extension loading +*/ + +//----------------------------------------------------------------------------- +// [SECTION] includes +//----------------------------------------------------------------------------- + +#include +#include "pl.h" +#include "pl_example_ext.h" + +//----------------------------------------------------------------------------- +// [SECTION] public api implementation +//----------------------------------------------------------------------------- + +static void +pl__example_print_to_console(const char* pcText) +{ + printf("%s\n", pcText); +} + +//----------------------------------------------------------------------------- +// [SECTION] extension loading +//----------------------------------------------------------------------------- + +PL_EXPORT void +pl_load_ext(plApiRegistryI* ptApiRegistry, bool bReload) +{ + const plExampleI tApi = { + .print_to_console = pl__example_print_to_console + }; + pl_set_api(ptApiRegistry, plExampleI, &tApi); +} + +PL_EXPORT void +pl_unload_ext(plApiRegistryI* ptApiRegistry, bool bReload) +{ + + if(bReload) + return; + + const plExampleI* ptApi = pl_get_api_latest(ptApiRegistry, plExampleI); + ptApiRegistry->remove_api(ptApi); +} diff --git a/extensions/pl_example_ext.h b/extensions/pl_example_ext.h new file mode 100644 index 0000000..44dc520 --- /dev/null +++ b/extensions/pl_example_ext.h @@ -0,0 +1,35 @@ +/* + pl_example_ext.h + - example extension +*/ + +/* +Index of this file: +// [SECTION] header mess +// [SECTION] apis +// [SECTION] public api +*/ + +//----------------------------------------------------------------------------- +// [SECTION] header mess +//----------------------------------------------------------------------------- + +#ifndef PL_EXAMPLE_EXT_H +#define PL_EXAMPLE_EXT_H + +//----------------------------------------------------------------------------- +// [SECTION] apis +//----------------------------------------------------------------------------- + +#define plExampleI_version (plVersion){1, 0, 0} + +//----------------------------------------------------------------------------- +// [SECTION] public api +//----------------------------------------------------------------------------- + +typedef struct _plExampleI +{ + void (*print_to_console)(const char* text); +} plExampleI; + +#endif // PL_EXAMPLE_EXT_H \ No newline at end of file diff --git a/scripts/gen_build.py b/scripts/gen_build.py new file mode 100644 index 0000000..24b06a7 --- /dev/null +++ b/scripts/gen_build.py @@ -0,0 +1,198 @@ +# gen_build.py + +# Index of this file: +# [SECTION] imports + +#----------------------------------------------------------------------------- +# [SECTION] imports +#----------------------------------------------------------------------------- + +import os +import sys +import platform as plat + +pilotlight_location = "../../pilotlight" + +if len(sys.argv) > 1: + pilotlight_location = sys.argv[1] + +sys.path.append(os.path.dirname(os.path.abspath(__file__)) + "/" + pilotlight_location) + +import pl_build.core as pl +import pl_build.backend_win32 as win32 +import pl_build.backend_linux as linux +import pl_build.backend_macos as apple + +#----------------------------------------------------------------------------- +# [SECTION] project +#----------------------------------------------------------------------------- + + + + +# where to output build scripts +working_directory = os.path.dirname(os.path.abspath(__file__)) + "/../src" + +with pl.project("game"): + + # used to decide hot reloading + pl.set_hot_reload_target(pilotlight_location + "/out/pilot_light") + + # project wide settings + pl.set_output_directory(pilotlight_location + "/out") + pl.add_link_directories(pilotlight_location + "/out") + pl.add_include_directories( + "../src", + "../extensions", + pilotlight_location + "/src", + pilotlight_location + "/libs", + pilotlight_location + "/extensions", + pilotlight_location + "/dependencies/stb") + + #----------------------------------------------------------------------------- + # [SECTION] profiles + #----------------------------------------------------------------------------- + + # win32 or msvc only + pl.add_profile(compiler_filter=["msvc"], + target_type_filter=[pl.TargetType.DYNAMIC_LIBRARY], + linker_flags=["-noimplib", "-noexp"]) + + pl.add_profile(compiler_filter=["msvc"], + linker_flags=["-incremental:no"], + compiler_flags=["-Zc:preprocessor", "-nologo", "-std:c11", "-W4", "-WX", "-wd4201", + "-wd4100", "-wd4996", "-wd4505", "-wd4189", "-wd5105", "-wd4115", "-permissive-"]) + pl.add_profile(compiler_filter=["msvc"], + configuration_filter=["debug"], + compiler_flags=["-Od", "-MDd", "-Zi"]) + pl.add_profile(compiler_filter=["msvc"], + configuration_filter=["release"], + compiler_flags=["-O2", "-MD"]) + + + # linux or gcc only + pl.add_profile(platform_filter=["Linux"], + link_directories=["/usr/lib/x86_64-linux-gnu"]) + pl.add_profile(compiler_filter=["gcc"], + linker_flags=["-ldl", "-lm"], + compiler_flags=["-std=gnu11", "-fPIC"]) + pl.add_profile(compiler_filter=["gcc"], + configuration_filter=["debug"], + compiler_flags=["--debug", "-g"]) + + # macos or clang only + pl.add_profile(platform_filter=["Darwin"], + link_frameworks=["Metal", "MetalKit", "Cocoa", "IOKit", "CoreVideo", "QuartzCore"]) + pl.add_profile(compiler_filter=["clang"], + linker_flags=["-Wl,-rpath,/usr/local/lib"], + compiler_flags=["-std=c99", "-fmodules", "-ObjC", "-fPIC"]) + pl.add_profile(compiler_filter=["clang"], + configuration_filter=["debug"], + compiler_flags=["--debug", "-g"]) + + # configs + pl.add_profile(configuration_filter=["debug"], definitions=["_DEBUG", "PL_CONFIG_DEBUG"]) + pl.add_profile(configuration_filter=["release"], definitions=["NDEBUG", "PL_CONFIG_RELEASE"]) + + #----------------------------------------------------------------------------- + # [SECTION] extensions + #----------------------------------------------------------------------------- + + with pl.target("pl_example_ext", pl.TargetType.DYNAMIC_LIBRARY, True): + + pl.add_source_files("../extensions/pl_example_ext.c") + pl.set_output_binary("pl_example_ext") + + # default config + with pl.configuration("debug"): + + # win32 + with pl.platform("Windows"): + + with pl.compiler("msvc"): + pass + + # linux + with pl.platform("Linux"): + with pl.compiler("gcc"): + pass + + # macos + with pl.platform("Darwin"): + with pl.compiler("clang"): + pass + + # release + with pl.configuration("release"): + + # win32 + with pl.platform("Windows"): + + with pl.compiler("msvc"): + pass + + # linux + with pl.platform("Linux"): + with pl.compiler("gcc"): + pass + + # macos + with pl.platform("Darwin"): + with pl.compiler("clang"): + pass + + #----------------------------------------------------------------------------- + # [SECTION] app + #----------------------------------------------------------------------------- + + with pl.target("template_app", pl.TargetType.DYNAMIC_LIBRARY, True): + + pl.add_source_files("../src/app.c") + pl.set_output_binary("template_app") + + # default config + with pl.configuration("debug"): + + # win32 + with pl.platform("Windows"): + with pl.compiler("msvc"): + pass + + # linux + with pl.platform("Linux"): + with pl.compiler("gcc"): + pass + + # mac os + with pl.platform("Darwin"): + with pl.compiler("clang"): + pass + + # release + with pl.configuration("release"): + + # win32 + with pl.platform("Windows"): + with pl.compiler("msvc"): + pass + + # linux + with pl.platform("Linux"): + with pl.compiler("gcc"): + pass + + # mac os + with pl.platform("Darwin"): + with pl.compiler("clang"): + pass + +#----------------------------------------------------------------------------- +# [SECTION] generate scripts +#----------------------------------------------------------------------------- + +if plat.system() == "Windows": + win32.generate_build(working_directory + '/' + "build.bat") +elif plat.system() == "Darwin": + apple.generate_build(working_directory + '/' + "build.sh") +elif plat.system() == "Linux": + linux.generate_build(working_directory + '/' + "build.sh") \ No newline at end of file diff --git a/scripts/setup.py b/scripts/setup.py new file mode 100644 index 0000000..551e2ec --- /dev/null +++ b/scripts/setup.py @@ -0,0 +1,135 @@ +import os +import platform + +defines = [ + "_DEBUG", + "PL_PROFILE_ON", + "PL_LOG_IMPLEMENTATION", + "PL_MEMORY_IMPLEMENTATION", + "PL_PROFILE_IMPLEMENTATION", + "PL_LOG_ON", + "PL_MEMORY_IMPLEMENTATION", + "PL_STL_IMPLEMENTATION", + "PL_STRING_IMPLEMENTATION", + "PL_MATH_INCLUDE_FUNCTIONS", + "PL_JSON_IMPLEMENTATION", + "PL_VULKAN_BACKEND", + "PL_METAL_BACKEND", + "PL_TEST_IMPLEMENTATION", + "PL_CONFIG_DEBUG" +] + +includes = [ + "${workspaceFolder}/**", + "${workspaceFolder}/src", + "${workspaceFolder}/extensions", + "${workspaceFolder}/../pilotlight/sandbox", + "${workspaceFolder}/../pilotlight/src", + "${workspaceFolder}/../pilotlight/libs", + "${workspaceFolder}/../pilotlight/extensions", + "${workspaceFolder}/../pilotlight/dependencies/stb", + "${workspaceFolder}/../pilotlight/dependencies/cgltf" +] + +######################################################################################################################## +# setup vs code +######################################################################################################################## + +if not os.path.isdir('../.vscode'): + os.mkdir('../.vscode') + +with open('../.vscode/launch.json', 'w') as file: + + lines = [] + lines.append('{') + lines.append(' "version": "0.2.0",') + lines.append(' "configurations": [') + lines.append(' {') + + if platform.system() == "Windows": + lines.append(' "name": "(Windows) Launch",') + lines.append(' "type": "cppvsdbg",') + lines.append(' "program": "${workspaceFolder}/../pilotlight/out/pilot_light.exe",') + lines.append(' "console": "integratedTerminal",') + + elif platform.system() == "Darwin": + lines.append(' "name": "(lldb) Launch",') + lines.append(' "type": "cppdbg",') + lines.append(' "program": "${workspaceFolder}/../pilotlight/out/pilot_light",') + lines.append(' "externalConsole": false,') + lines.append(' "MIMode": "lldb",') + + elif platform.system() == "Linux": + lines.append(' "name": "(lldb) Launch",') + lines.append(' "type": "cppdbg",') + lines.append(' "program": "${workspaceFolder}/../pilotlight/out/pilot_light",') + + lines.append(' "request": "launch",') + lines.append(' "args": ["-a", "template_app"],') + lines.append(' "stopAtEntry": false,') + lines.append(' "cwd": "${workspaceFolder}/../pilotlight/out/",') + lines.append(' "environment": []') + lines.append(' }') + lines.append(' ]') + lines.append('}') + + for i in range(len(lines)): + lines[i] = lines[i] + "\n" + file.writelines(lines) + +with open('../.vscode/c_cpp_properties.json', 'w') as file: + lines = [] + lines.append('{') + lines.append(' "version" : 4,') + lines.append(' "configurations": [') + lines.append(' {') + + if platform.system() == "Windows": + lines.append(' "name": "Win32",') + elif platform.system() == "Darwin": + lines.append(' "name": "Apple",') + elif platform.system() == "Linux": + lines.append(' "name": "Linux",') + + lines.append(' "includePath": [') + for i in range(len(includes) - 1): + lines.append(' "' + includes[i] + '",') + lines.append(' "' + includes[-1] + '"') + lines.append(' ],') + lines.append(' "defines": [') + for i in range(len(defines) - 1): + lines.append(' "' + defines[i] + '",') + lines.append(' "' + defines[-1] + '"') + lines.append(' ],') + lines.append(' "cStandard": "c11",') + + if platform.system() == "Windows": + lines.append(' "windowsSdkVersion": "10.0.19041.0",') + lines.append(' "intelliSenseMode": "windows-msvc-x64"') + elif platform.system() == "Darwin": + lines.append(' "intelliSenseMode": "macos-clang-arm64"') + elif platform.system() == "Linux": + lines.append(' "intelliSenseMode": "linux-gcc-x64"') + + lines.append(' }') + lines.append(' ]') + lines.append('}') + + for i in range(len(lines)): + lines[i] = lines[i] + "\n" + file.writelines(lines) + +with open('../.vscode/settings.json', 'w') as file: + lines = [] + lines.append('{') + lines.append(' "files.associations": {') + lines.append(' "pl_*.h": "c",') + lines.append(' "pl_*.m": "objective-c",') + lines.append(' "pl_*.inc": "c"') + lines.append(' },') + lines.append(' "python.analysis.extraPaths": ["../pilotlight/pl_build"]') + lines.append('}') + + for i in range(len(lines)): + lines[i] = lines[i] + "\n" + file.writelines(lines) diff --git a/src/app.c b/src/app.c new file mode 100644 index 0000000..c978b39 --- /dev/null +++ b/src/app.c @@ -0,0 +1,569 @@ +/* + app.c + - template app + - loads only stable APIs +*/ + +/* +Index of this file: +// [SECTION] includes +// [SECTION] structs +// [SECTION] apis +// [SECTION] helper functions forward declarations +// [SECTION] pl_app_load +// [SECTION] pl_app_shutdown +// [SECTION] pl_app_resize +// [SECTION] pl_app_update +// [SECTION] helper functions implementations +*/ + +//----------------------------------------------------------------------------- +// [SECTION] includes +//----------------------------------------------------------------------------- + +#include // malloc, free +#include // memset +#include "pl.h" +#include "pl_memory.h" +#define PL_MATH_INCLUDE_FUNCTIONS +#include "pl_math.h" + +// extensions +#include "pl_log_ext.h" +#include "pl_window_ext.h" +#include "pl_shader_ext.h" +#include "pl_draw_ext.h" +#include "pl_ui_ext.h" +#include "pl_graphics_ext.h" +#include "pl_draw_backend_ext.h" +#include "pl_profile_ext.h" +#include "pl_stats_ext.h" +#include "pl_job_ext.h" +#include "pl_string_intern_ext.h" +#include "pl_network_ext.h" +#include "pl_threads_ext.h" +#include "pl_atomics_ext.h" +#include "pl_library_ext.h" +#include "pl_file_ext.h" +#include "pl_rect_pack_ext.h" +#include "pl_gpu_allocators_ext.h" +#include "pl_image_ext.h" +#include "pl_virtual_memory_ext.h" +#include "pl_debug_ext.h" // not technically stable + +// example extensions +#include "pl_example_ext.h" + +//----------------------------------------------------------------------------- +// [SECTION] structs +//----------------------------------------------------------------------------- + +typedef struct _plAppData +{ + // window + plWindow* ptWindow; + + // drawing stuff + plDrawList2D* ptDrawlist; + plDrawLayer2D* ptFGLayer; + plDrawLayer2D* ptBGLayer; + plFont* ptDefaultFont; + + // ui options + plDebugApiInfo tDebugInfo; + bool bShowUiDebug; + bool bShowUiStyle; + + // graphics & sync objects + plDevice* ptDevice; + plSurface* ptSurface; + plSwapchain* ptSwapchain; + plTimelineSemaphore* aptSemaphores[PL_MAX_FRAMES_IN_FLIGHT]; + uint64_t aulNextTimelineValue[PL_MAX_FRAMES_IN_FLIGHT]; + plCommandPool* atCmdPools[PL_MAX_FRAMES_IN_FLIGHT]; + plRenderPassHandle tMainRenderPass; + plRenderPassLayoutHandle tMainRenderPassLayout; + +} plAppData; + +//----------------------------------------------------------------------------- +// [SECTION] apis +//----------------------------------------------------------------------------- + +const plMemoryI* gptMemory = NULL; +const plIOI* gptIO = NULL; +const plWindowI* gptWindows = NULL; +const plGraphicsI* gptGfx = NULL; +const plDrawI* gptDraw = NULL; +const plUiI* gptUi = NULL; +const plShaderI* gptShader = NULL; +const plDrawBackendI* gptDrawBackend = NULL; +const plProfileI* gptProfile = NULL; +const plExampleI* gptExample = NULL; +const plStatsI* gptStats = NULL; +const plDebugApiI* gptDebug = NULL; +const plImageI* gptImage = NULL; +const plGPUAllocatorsI* gptGpuAllocators = NULL; +const plJobI* gptJob = NULL; +const plThreadsI* gptThreads = NULL; +const plAtomicsI* gptAtomics = NULL; +const plRectPackI* gptRect = NULL; +const plFileI* gptFile = NULL; +const plNetworkI* gptNetwork = NULL; +const plStringInternI* gptString = NULL; +const plLibraryI* gptLibrary = NULL; +const plLogI* gptLog = NULL; +const plVirtualMemoryI* gptVirtualMemory = NULL; + +// helpers +#define PL_ALLOC(x) gptMemory->tracked_realloc(NULL, (x), __FILE__, __LINE__) +#define PL_REALLOC(x, y) gptMemory->tracked_realloc((x), (y), __FILE__, __LINE__) +#define PL_FREE(x) gptMemory->tracked_realloc((x), 0, __FILE__, __LINE__) + +#define PL_DS_ALLOC(x) gptMemory->tracked_realloc(NULL, (x), __FILE__, __LINE__) +#define PL_DS_ALLOC_INDIRECT(x, FILE, LINE) gptMemory->tracked_realloc(NULL, (x), FILE, LINE) +#define PL_DS_FREE(x) gptMemory->tracked_realloc((x), 0, __FILE__, __LINE__) +#include "pl_ds.h" + +//----------------------------------------------------------------------------- +// [SECTION] helper functions forward declarations +//----------------------------------------------------------------------------- + +void pl__setup_graphics_extensions (plAppData*); +void pl__resize_graphics_extensions(plAppData*); +void pl__resize_graphics_extensions(plAppData*); + +//----------------------------------------------------------------------------- +// [SECTION] pl_app_load +//----------------------------------------------------------------------------- + +PL_EXPORT void* +pl_app_load(plApiRegistryI* ptApiRegistry, plAppData* ptAppData) +{ + // NOTE: on first load, "ptAppData" 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 = pl_get_api_latest(ptApiRegistry, plDataRegistryI); + + // if "ptAppData" is a valid pointer, then this function is being called + // during a hot reload. + if(ptAppData) + { + + // re-retrieve the apis since we are now in + // a different dll/so + gptMemory = pl_get_api_latest(ptApiRegistry, plMemoryI); + gptIO = pl_get_api_latest(ptApiRegistry, plIOI); + gptWindows = pl_get_api_latest(ptApiRegistry, plWindowI); + gptGfx = pl_get_api_latest(ptApiRegistry, plGraphicsI); + gptDraw = pl_get_api_latest(ptApiRegistry, plDrawI); + gptShader = pl_get_api_latest(ptApiRegistry, plShaderI); + gptDrawBackend = pl_get_api_latest(ptApiRegistry, plDrawBackendI); + gptUi = pl_get_api_latest(ptApiRegistry, plUiI); + gptProfile = pl_get_api_latest(ptApiRegistry, plProfileI); + gptStats = pl_get_api_latest(ptApiRegistry, plStatsI); + gptExample = pl_get_api_latest(ptApiRegistry, plExampleI); + gptDebug = pl_get_api_latest(ptApiRegistry, plDebugApiI); + gptImage = pl_get_api_latest(ptApiRegistry, plImageI); + gptGpuAllocators = pl_get_api_latest(ptApiRegistry, plGPUAllocatorsI); + gptJob = pl_get_api_latest(ptApiRegistry, plJobI); + gptThreads = pl_get_api_latest(ptApiRegistry, plThreadsI); + gptAtomics = pl_get_api_latest(ptApiRegistry, plAtomicsI); + gptRect = pl_get_api_latest(ptApiRegistry, plRectPackI); + gptFile = pl_get_api_latest(ptApiRegistry, plFileI); + gptNetwork = pl_get_api_latest(ptApiRegistry, plNetworkI); + gptString = pl_get_api_latest(ptApiRegistry, plStringInternI); + gptLibrary = pl_get_api_latest(ptApiRegistry, plLibraryI); + gptLog = pl_get_api_latest(ptApiRegistry, plLogI); + gptVirtualMemory = pl_get_api_latest(ptApiRegistry, plVirtualMemoryI); + + return ptAppData; + } + + //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~apis & extensions~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + // retrieve extension registry + const plExtensionRegistryI* ptExtensionRegistry = pl_get_api_latest(ptApiRegistry, plExtensionRegistryI); + + // load extensions + ptExtensionRegistry->load("pl_unity_ext", NULL, NULL, false); + ptExtensionRegistry->load("pl_example_ext", NULL, NULL, true); + + // load apis + gptMemory = pl_get_api_latest(ptApiRegistry, plMemoryI); + gptIO = pl_get_api_latest(ptApiRegistry, plIOI); + gptWindows = pl_get_api_latest(ptApiRegistry, plWindowI); + gptGfx = pl_get_api_latest(ptApiRegistry, plGraphicsI); + gptDraw = pl_get_api_latest(ptApiRegistry, plDrawI); + gptShader = pl_get_api_latest(ptApiRegistry, plShaderI); + gptDrawBackend = pl_get_api_latest(ptApiRegistry, plDrawBackendI); + gptUi = pl_get_api_latest(ptApiRegistry, plUiI); + gptProfile = pl_get_api_latest(ptApiRegistry, plProfileI); + gptStats = pl_get_api_latest(ptApiRegistry, plStatsI); + gptExample = pl_get_api_latest(ptApiRegistry, plExampleI); + gptDebug = pl_get_api_latest(ptApiRegistry, plDebugApiI); + gptImage = pl_get_api_latest(ptApiRegistry, plImageI); + gptGpuAllocators = pl_get_api_latest(ptApiRegistry, plGPUAllocatorsI); + gptJob = pl_get_api_latest(ptApiRegistry, plJobI); + gptThreads = pl_get_api_latest(ptApiRegistry, plThreadsI); + gptAtomics = pl_get_api_latest(ptApiRegistry, plAtomicsI); + gptRect = pl_get_api_latest(ptApiRegistry, plRectPackI); + gptFile = pl_get_api_latest(ptApiRegistry, plFileI); + gptNetwork = pl_get_api_latest(ptApiRegistry, plNetworkI); + gptString = pl_get_api_latest(ptApiRegistry, plStringInternI); + gptLibrary = pl_get_api_latest(ptApiRegistry, plLibraryI); + gptLog = pl_get_api_latest(ptApiRegistry, plLogI); + gptVirtualMemory = pl_get_api_latest(ptApiRegistry, plVirtualMemoryI); + + // this path is taken only during first load, so we + // allocate app memory here + ptAppData = PL_ALLOC(sizeof(plAppData)); + memset(ptAppData, 0, sizeof(plAppData)); + + // use window API to create a window + plWindowDesc tWindowDesc = { + .pcTitle = "App Template", + .iXPos = 200, + .iYPos = 200, + .uWidth = 500, + .uHeight = 500, + }; + gptWindows->create_window(tWindowDesc, &ptAppData->ptWindow); + + // setup graphics extension + pl__setup_graphics_extensions(ptAppData); + + //~~~~~~~~~~~~~~~~~~~~~~~~~~~~setup draw extensions~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + // initialize + gptDraw->initialize(NULL); + gptDrawBackend->initialize(ptAppData->ptDevice); + + // create font atlas + plFontAtlas* ptAtlas = gptDraw->create_font_atlas(); + gptDraw->set_font_atlas(ptAtlas); + ptAppData->ptDefaultFont = gptDraw->add_default_font(ptAtlas); + + // build font atlas + plCommandPool* ptCmdPool = ptAppData->atCmdPools[gptGfx->get_current_frame_index()]; + plCommandBuffer* ptCmdBuffer = gptGfx->request_command_buffer(ptCmdPool); + gptDrawBackend->build_font_atlas(ptCmdBuffer, ptAtlas); + gptGfx->return_command_buffer(ptCmdBuffer); + + //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ui extension~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + gptUi->initialize(); + gptUi->set_default_font(ptAppData->ptDefaultFont); + + //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~app stuff~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + // create drawlist and some layers to draw to + ptAppData->ptDrawlist = gptDraw->request_2d_drawlist(); + ptAppData->ptFGLayer = gptDraw->request_2d_layer(ptAppData->ptDrawlist); + ptAppData->ptBGLayer = gptDraw->request_2d_layer(ptAppData->ptDrawlist); + + // demonstrate example extension + gptExample->print_to_console("From example extension!"); + + // return app memory + return ptAppData; +} + +//----------------------------------------------------------------------------- +// [SECTION] pl_app_shutdown +//----------------------------------------------------------------------------- + +PL_EXPORT void +pl_app_shutdown(plAppData* ptAppData) +{ + // ensure GPU is finished before cleanup + gptGfx->flush_device(ptAppData->ptDevice); + for(uint32_t i = 0; i < gptGfx->get_frames_in_flight(); i++) + { + gptGfx->cleanup_command_pool(ptAppData->atCmdPools[i]); + gptGfx->cleanup_semaphore(ptAppData->aptSemaphores[i]); + } + gptDrawBackend->cleanup_font_atlas(NULL); + gptUi->cleanup(); + gptDrawBackend->cleanup(); + gptGfx->cleanup_swapchain(ptAppData->ptSwapchain); + gptGfx->cleanup_surface(ptAppData->ptSurface); + gptGfx->cleanup_device(ptAppData->ptDevice); + gptGfx->cleanup(); + gptWindows->destroy_window(ptAppData->ptWindow); + PL_FREE(ptAppData); +} + +//----------------------------------------------------------------------------- +// [SECTION] pl_app_resize +//----------------------------------------------------------------------------- + +PL_EXPORT void +pl_app_resize(plAppData* ptAppData) +{ + pl__resize_graphics_extensions(ptAppData); +} + +//----------------------------------------------------------------------------- +// [SECTION] pl_app_update +//----------------------------------------------------------------------------- + +PL_EXPORT void +pl_app_update(plAppData* ptAppData) +{ + gptProfile->begin_frame(); + pl_begin_cpu_sample(gptProfile, 0, __FUNCTION__); + + // for convience + plIO* ptIO = gptIO->get_io(); + + // new frame stuff + gptIO->new_frame(); + gptDrawBackend->new_frame(); + gptUi->new_frame(); + gptStats->new_frame(); + gptGfx->begin_frame(ptAppData->ptDevice); + plCommandPool* ptCmdPool = ptAppData->atCmdPools[gptGfx->get_current_frame_index()]; + gptGfx->reset_command_pool(ptCmdPool, 0); + + // update statistics + static double* pdFrameTimeCounter = NULL; + if(!pdFrameTimeCounter) + pdFrameTimeCounter = gptStats->get_counter("frametime (ms)"); + *pdFrameTimeCounter = (double)ptIO->fDeltaTime * 1000.0; + + // acquire swapchain image + if(!gptGfx->acquire_swapchain_image(ptAppData->ptSwapchain)) + { + pl_app_resize(ptAppData); + pl_end_cpu_sample(gptProfile, 0); + gptProfile->end_frame(); + return; + } + + // just some drawing + gptDraw->add_circle(ptAppData->ptFGLayer, (plVec2){100.0f, 100.0f}, 50.0f, 12, (plDrawLineOptions){.fThickness = 2.0f, .uColor = PL_COLOR_32_RGBA(1.0f, 0.0f, 1.0f, 1.0f)}); + + if(gptUi->begin_window("Pilot Light", NULL, false)) + { + + const float pfRatios[] = {1.0f}; + gptUi->layout_row(PL_UI_LAYOUT_ROW_TYPE_DYNAMIC, 0.0f, 1, pfRatios); + if(gptUi->begin_collapsing_header("Information", 0)) + { + + gptUi->text("Pilot Light %s", PILOT_LIGHT_VERSION_STRING); + gptUi->end_collapsing_header(); + } + if(gptUi->begin_collapsing_header("Tools", 0)) + { + 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->end_collapsing_header(); + } + if(gptUi->begin_collapsing_header("User Interface", 0)) + { + gptUi->checkbox("UI Debug", &ptAppData->bShowUiDebug); + gptUi->checkbox("UI Style", &ptAppData->bShowUiStyle); + gptUi->end_collapsing_header(); + } + gptUi->end_window(); + } + + if(ptAppData->bShowUiStyle) + gptUi->show_style_editor_window(&ptAppData->bShowUiStyle); + + if(ptAppData->bShowUiDebug) + gptUi->show_debug_window(&ptAppData->bShowUiDebug); + + gptDebug->show_debug_windows(&ptAppData->tDebugInfo); + + //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~graphics work~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + const uint32_t uCurrentFrameIndex = gptGfx->get_current_frame_index(); + + plCommandBuffer* ptCommandBuffer = gptGfx->request_command_buffer(ptCmdPool); + + const plBeginCommandInfo tBeginInfo = { + .uWaitSemaphoreCount = 1, + .atWaitSempahores = {ptAppData->aptSemaphores[uCurrentFrameIndex]}, + .auWaitSemaphoreValues = {ptAppData->aulNextTimelineValue[uCurrentFrameIndex]}, + }; + gptGfx->begin_command_recording(ptCommandBuffer, &tBeginInfo); + + // begin main renderpass (directly to swapchain) + plRenderEncoder* ptEncoder = gptGfx->begin_render_pass(ptCommandBuffer, ptAppData->tMainRenderPass, NULL); + + // submit our layers & drawlist + gptDraw->submit_2d_layer(ptAppData->ptBGLayer); + gptDraw->submit_2d_layer(ptAppData->ptFGLayer); + gptDrawBackend->submit_2d_drawlist(ptAppData->ptDrawlist, ptEncoder, ptIO->tMainViewportSize.x, ptIO->tMainViewportSize.y, gptGfx->get_swapchain_info(ptAppData->ptSwapchain).tSampleCount); + + // submits UI layers + gptUi->end_frame(); + + // submit UI drawlists + gptDrawBackend->submit_2d_drawlist(gptUi->get_draw_list(), ptEncoder, ptIO->tMainViewportSize.x, ptIO->tMainViewportSize.y, gptGfx->get_swapchain_info(ptAppData->ptSwapchain).tSampleCount); + gptDrawBackend->submit_2d_drawlist(gptUi->get_debug_draw_list(), ptEncoder, ptIO->tMainViewportSize.x, ptIO->tMainViewportSize.y, gptGfx->get_swapchain_info(ptAppData->ptSwapchain).tSampleCount); + + // end render pass + gptGfx->end_render_pass(ptEncoder); + + // end recording + gptGfx->end_command_recording(ptCommandBuffer); + + //~~~~~~~~~~~~~~~~~~~~~~~~~~submit work to GPU & present~~~~~~~~~~~~~~~~~~~~~~~ + + const plSubmitInfo tSubmitInfo = { + .uSignalSemaphoreCount = 1, + .atSignalSempahores = {ptAppData->aptSemaphores[uCurrentFrameIndex]}, + .auSignalSemaphoreValues = {++ptAppData->aulNextTimelineValue[uCurrentFrameIndex]}, + }; + + if(!gptGfx->present(ptCommandBuffer, &tSubmitInfo, &ptAppData->ptSwapchain, 1)) + pl_app_resize(ptAppData); + + gptGfx->return_command_buffer(ptCommandBuffer); + + pl_end_cpu_sample(gptProfile, 0); + gptProfile->end_frame(); +} + +//----------------------------------------------------------------------------- +// [SECTION] helper functions implementations +//----------------------------------------------------------------------------- + +void +pl__setup_graphics_extensions(plAppData* ptAppData) +{ + // initialize shader extension (shader compiler) + static const plShaderOptions tDefaultShaderOptions = { + .apcIncludeDirectories = { + "../shaders/" + }, + .apcDirectories = { + "../shaders/" + }, + .tFlags = PL_SHADER_FLAGS_AUTO_OUTPUT + }; + gptShader->initialize(&tDefaultShaderOptions); + + // initialize graphics system + const plGraphicsInit tGraphicsInit = { + .tFlags = PL_GRAPHICS_INIT_FLAGS_VALIDATION_ENABLED | PL_GRAPHICS_INIT_FLAGS_SWAPCHAIN_ENABLED + }; + gptGfx->initialize(&tGraphicsInit); + ptAppData->ptSurface = gptGfx->create_surface(ptAppData->ptWindow); + + // find suitable device + uint32_t uDeviceCount = 16; + plDeviceInfo atDeviceInfos[16] = {0}; + gptGfx->enumerate_devices(atDeviceInfos, &uDeviceCount); + + // we will prefer discrete, then integrated + int iBestDvcIdx = 0; + int iDiscreteGPUIdx = -1; + int iIntegratedGPUIdx = -1; + for(uint32_t i = 0; i < uDeviceCount; i++) + { + + if(atDeviceInfos[i].tType == PL_DEVICE_TYPE_DISCRETE) + iDiscreteGPUIdx = i; + else if(atDeviceInfos[i].tType == PL_DEVICE_TYPE_INTEGRATED) + iIntegratedGPUIdx = i; + } + + if(iDiscreteGPUIdx > -1) + iBestDvcIdx = iDiscreteGPUIdx; + else if(iIntegratedGPUIdx > -1) + iBestDvcIdx = iIntegratedGPUIdx; + + // create device + const plDeviceInit tDeviceInit = { + .uDeviceIdx = iBestDvcIdx, + .ptSurface = ptAppData->ptSurface + }; + ptAppData->ptDevice = gptGfx->create_device(&tDeviceInit); + + // create command pools + for(uint32_t i = 0; i < gptGfx->get_frames_in_flight(); i++) + ptAppData->atCmdPools[i] = gptGfx->create_command_pool(ptAppData->ptDevice, NULL); + + // create swapchain + plSwapchainInit tSwapInit = { + .tSampleCount = PL_SAMPLE_COUNT_1 + }; + ptAppData->ptSwapchain = gptGfx->create_swapchain(ptAppData->ptDevice, ptAppData->ptSurface, &tSwapInit); + + uint32_t uImageCount = 0; + plTextureHandle* atSwapchainImages = gptGfx->get_swapchain_images(ptAppData->ptSwapchain, &uImageCount); + + // create main render pass layout + const plRenderPassLayoutDesc tMainRenderPassLayoutDesc = { + .atRenderTargets = { + { .tFormat = gptGfx->get_swapchain_info(ptAppData->ptSwapchain).tFormat }, // swapchain + }, + .atSubpasses = { + { + .uRenderTargetCount = 1, + .auRenderTargets = {0} + } + } + }; + ptAppData->tMainRenderPassLayout = gptGfx->create_render_pass_layout(ptAppData->ptDevice, &tMainRenderPassLayoutDesc); + + // create main render pass + const plRenderPassDesc tMainRenderPassDesc = { + .tLayout = ptAppData->tMainRenderPassLayout, + .atColorTargets = { + { + .tLoadOp = PL_LOAD_OP_CLEAR, + .tStoreOp = PL_STORE_OP_STORE, + .tCurrentUsage = PL_TEXTURE_USAGE_UNSPECIFIED, + .tNextUsage = PL_TEXTURE_USAGE_PRESENT, + .tClearColor = {0.0f, 0.0f, 0.0f, 1.0f} + } + }, + .tDimensions = {(float)gptGfx->get_swapchain_info(ptAppData->ptSwapchain).uWidth, (float)gptGfx->get_swapchain_info(ptAppData->ptSwapchain).uHeight}, + .ptSwapchain = ptAppData->ptSwapchain + }; + + plRenderPassAttachments atMainAttachmentSets[16] = {0}; + for(uint32_t i = 0; i < uImageCount; i++) + { + atMainAttachmentSets[i].atViewAttachments[0] = atSwapchainImages[i]; + } + ptAppData->tMainRenderPass = gptGfx->create_render_pass(ptAppData->ptDevice, &tMainRenderPassDesc, atMainAttachmentSets); + + // create timeline semaphores to syncronize GPU work submission + for(uint32_t i = 0; i < gptGfx->get_frames_in_flight(); i++) + ptAppData->aptSemaphores[i] = gptGfx->create_semaphore(ptAppData->ptDevice, false); +} + +void +pl__resize_graphics_extensions(plAppData* ptAppData) +{ + plIO* ptIO = gptIO->get_io(); + plSwapchainInit tDesc = { + .bVSync = true, + .uWidth = (uint32_t)ptIO->tMainViewportSize.x, + .uHeight = (uint32_t)ptIO->tMainViewportSize.y, + .tSampleCount = gptGfx->get_swapchain_info(ptAppData->ptSwapchain).tSampleCount, + }; + gptGfx->recreate_swapchain(ptAppData->ptSwapchain, &tDesc); + + uint32_t uImageCount = 0; + plTextureHandle* atSwapchainImages = gptGfx->get_swapchain_images(ptAppData->ptSwapchain, &uImageCount); + + plRenderPassAttachments atMainAttachmentSets[16] = {0}; + for(uint32_t i = 0; i < uImageCount; i++) + { + atMainAttachmentSets[i].atViewAttachments[0] = atSwapchainImages[i]; + } + gptGfx->update_render_pass_attachments(ptAppData->ptDevice, ptAppData->tMainRenderPass, gptIO->get_io()->tMainViewportSize, atMainAttachmentSets); +}