initial vulkan only

This commit is contained in:
Jonathan Hoffstadt 2025-08-13 10:50:35 -05:00
parent 998f922764
commit 79be18d11f
8 changed files with 392 additions and 368 deletions

View File

@ -56,7 +56,7 @@ From within a local directory, enter the following commands in your terminal:
# clone & build pilot light # clone & build pilot light
git clone https://github.com/PilotLightTech/pilotlight git clone https://github.com/PilotLightTech/pilotlight
cd pilotlight/src cd pilotlight/src
chmod +x build_linux.sh chmod +x build.sh -c moltenvk
./build_linux.sh ./build_linux.sh
# clone & build example # clone & build example

BIN
data/test.hdr Normal file

Binary file not shown.

View File

@ -1,52 +0,0 @@
/*
pl_example_ext.c
*/
/*
Index of this file:
// [SECTION] includes
// [SECTION] public api implementation
// [SECTION] extension loading
*/
//-----------------------------------------------------------------------------
// [SECTION] includes
//-----------------------------------------------------------------------------
#include <stdio.h>
#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);
}

View File

@ -1,35 +0,0 @@
/*
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 {1, 0, 0}
//-----------------------------------------------------------------------------
// [SECTION] public api
//-----------------------------------------------------------------------------
typedef struct _plExampleI
{
void (*print_to_console)(const char* text);
} plExampleI;
#endif // PL_EXAMPLE_EXT_H

View File

@ -57,9 +57,6 @@ with pl.project("game"):
pl.add_profile(compiler_filter=["msvc"], pl.add_profile(compiler_filter=["msvc"],
configuration_filter=["debug"], configuration_filter=["debug"],
compiler_flags=["-Od", "-MDd", "-Zi"]) compiler_flags=["-Od", "-MDd", "-Zi"])
pl.add_profile(compiler_filter=["msvc"],
configuration_filter=["release"],
compiler_flags=["-O2", "-MD"])
# linux or gcc only # linux or gcc only
@ -84,54 +81,6 @@ with pl.project("game"):
# configs # configs
pl.add_profile(configuration_filter=["debug"], definitions=["_DEBUG", "PL_CONFIG_DEBUG"]) 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 # [SECTION] app
@ -148,35 +97,21 @@ with pl.project("game"):
# win32 # win32
with pl.platform("Windows"): with pl.platform("Windows"):
with pl.compiler("msvc"): with pl.compiler("msvc"):
pass pl.add_static_link_libraries("vulkan-1")
pl.add_include_directories("%VULKAN_SDK%\\Include")
pl.add_link_directories('%VULKAN_SDK%\\Lib')
# linux # linux
with pl.platform("Linux"): with pl.platform("Linux"):
with pl.compiler("gcc"): with pl.compiler("gcc"):
pass pl.add_dynamic_link_libraries("vulkan")
pl.add_include_directories('$VULKAN_SDK/include', '/usr/include/vulkan')
pl.add_link_directories('$VULKAN_SDK/lib')
# mac os # mac os
with pl.platform("Darwin"): with pl.platform("Darwin"):
with pl.compiler("clang"): with pl.compiler("clang"):
pass pl.add_dynamic_link_libraries("vulkan")
# 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 # [SECTION] generate scripts

View File

@ -28,7 +28,8 @@ includes = [
"${workspaceFolder}/../pilotlight/libs", "${workspaceFolder}/../pilotlight/libs",
"${workspaceFolder}/../pilotlight/extensions", "${workspaceFolder}/../pilotlight/extensions",
"${workspaceFolder}/../pilotlight/dependencies/stb", "${workspaceFolder}/../pilotlight/dependencies/stb",
"${workspaceFolder}/../pilotlight/dependencies/cgltf" "${workspaceFolder}/../pilotlight/dependencies/cgltf",
"${env:VK_SDK_PATH}/Include"
] ]
######################################################################################################################## ########################################################################################################################

30
shaders/shader.comp Normal file
View File

@ -0,0 +1,30 @@
#version 450
layout(set = 0, binding = 0) buffer Pos {
float values[ ];
};
layout (local_size_x = 1, local_size_y = 1, local_size_z = 1) in;
void main()
{
// PRACTICE (how to get 6144 to the shader without hard coding?)
const uint row_stride = 6144 * 4;
uint pixel_x = gl_GlobalInvocationID.x;
uint pixel_y = gl_GlobalInvocationID.y;
// const int iXCoord = int(gl_WorkGroupID.x * 8 + gl_LocalInvocationID.x);
// original colors
float fOriginalRed = values[ pixel_x * 4 + pixel_y * row_stride];
float fOriginalGreen = values[ pixel_x * 4 + pixel_y * row_stride + 1];
float fOriginalBlue = values[ pixel_x * 4 + pixel_y * row_stride + 2];
float fOriginalAlpha = values[ pixel_x * 4 + pixel_y * row_stride + 3];
// swap red & green channels (and unnormalize)
values[pixel_x * 4 + pixel_y * row_stride] = clamp(fOriginalRed * 255, 0, 255);
values[pixel_x * 4 + pixel_y * row_stride + 1] = clamp(fOriginalGreen * 255, 0, 255);
values[pixel_x * 4 + pixel_y * row_stride + 2] = clamp(fOriginalBlue * 255, 0, 255);
values[pixel_x * 4 + pixel_y * row_stride + 3] = clamp(255, 0, 255);
}

559
src/app.c
View File

@ -1,15 +1,3 @@
/*
example_basic_2.c
- demonstrates loading APIs
- demonstrates loading extensions
- demonstrates hot reloading
- demonstrates starter extension
- demonstrates basic drawing extension (2D)
- demonstrates basic screen log extension
- demonstrates basic console extension
- demonstrates basic UI extension
*/
/* /*
Index of this file: Index of this file:
// [SECTION] quick notes // [SECTION] quick notes
@ -22,45 +10,6 @@ Index of this file:
// [SECTION] pl_app_update // [SECTION] pl_app_update
*/ */
//-----------------------------------------------------------------------------
// [SECTION] quick notes
//-----------------------------------------------------------------------------
/*
This example is the first to introduce extensions. Extensions are just
shared libraries that export a "load" and "unload" function. The default
being:
* void pl_load_ext (plApiRegistryI*, bool reload)
* void pl_unload_ext(plApiRegistryI*, bool reload)
Later examples will explain more about the details of creating an extension
but the important thing to understand now is that an extension just provides
an implementation of an API. The "load" function allows an extension to
request APIs it depends on and to register any API it provides. The unload
function just allows an extension the opportunity to unregister and perform
any required cleanup.
This example is also the first to introduce the "starter" extension. This
extension acts a bit as a helper extension to remove some common boilerplate
but is also just useful in general for most applications only needing to use
UI, plotting, drawing, etc. Or even to just experiment with the lower level
graphics extension in an isolated manner. Later examples will gradually peel
away at this extension and others. For this example, we will just demonstrate
some of the smaller helpful extensions. These include:
* log
* profile
* stat
* console
* screen log
* ui
This will be very light introductions with later examples going into more
detail. Feel free to open the header file for the extension for more
information and functionality.
*/
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
// [SECTION] includes // [SECTION] includes
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
@ -73,17 +22,17 @@ Index of this file:
#include "pl_math.h" #include "pl_math.h"
// extensions // extensions
#include "pl_draw_ext.h" #define PL_GRAPHICS_EXPOSE_VULKAN
#include "pl_graphics_ext.h"
#include "pl_shader_ext.h"
#include "pl_starter_ext.h" #include "pl_starter_ext.h"
#include "pl_ui_ext.h" #include "pl_image_ext.h"
#include "pl_vfs_ext.h"
#include "pl_screen_log_ext.h" #include "pl_screen_log_ext.h"
#include "pl_profile_ext.h"
#include "pl_log_ext.h"
#include "pl_stats_ext.h"
#include "pl_console_ext.h"
// out extension #include "vulkan/vulkan.h"
#include "pl_example_ext.h"
#define VULKAN_CHECK(x) assert(x == VK_SUCCESS)
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
// [SECTION] structs // [SECTION] structs
@ -93,12 +42,12 @@ typedef struct _plAppData
{ {
// window // window
plWindow* ptWindow; plWindow* ptWindow;
plSurface* ptSurface;
plDevice* ptDevice;
// log channel // shaders
uint64_t uExampleLogChannel; plShaderHandle tShader;
// console variable
bool bShowHelpWindow;
} plAppData; } plAppData;
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
@ -107,20 +56,12 @@ typedef struct _plAppData
const plIOI* gptIO = NULL; const plIOI* gptIO = NULL;
const plWindowI* gptWindows = NULL; const plWindowI* gptWindows = NULL;
const plDrawI* gptDraw = NULL; const plGraphicsI* gptGfx = NULL;
const plShaderI* gptShader = NULL;
const plStarterI* gptStarter = NULL; const plStarterI* gptStarter = NULL;
const plUiI* gptUI = NULL; const plImageI* gptImage = NULL;
const plVfsI* gptVfs = NULL;
const plScreenLogI* gptScreenLog = NULL; const plScreenLogI* gptScreenLog = NULL;
const plProfileI* gptProfile = NULL;
const plStatsI* gptStats = NULL;
const plMemoryI* gptMemory = NULL;
const plLogI* gptLog = NULL;
const plConsoleI* gptConsole = NULL;
const plExampleI* gptExample = NULL;
#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__)
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
// [SECTION] pl_app_load // [SECTION] pl_app_load
@ -129,76 +70,35 @@ const plExampleI* gptExample = NULL;
PL_EXPORT void* PL_EXPORT void*
pl_app_load(plApiRegistryI* ptApiRegistry, plAppData* ptAppData) 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
// if "ptAppData" is a valid pointer, then this function is being called // this path is taken only during first load, so we
// during a hot reload. // allocate app memory here
if(ptAppData) ptAppData = malloc(sizeof(plAppData));
{ memset(ptAppData, 0, sizeof(plAppData));
// re-retrieve the apis since we are now in
// a different dll/so
gptIO = pl_get_api_latest(ptApiRegistry, plIOI);
gptWindows = pl_get_api_latest(ptApiRegistry, plWindowI);
gptDraw = pl_get_api_latest(ptApiRegistry, plDrawI);
gptStarter = pl_get_api_latest(ptApiRegistry, plStarterI);
gptUI = pl_get_api_latest(ptApiRegistry, plUiI);
gptScreenLog = pl_get_api_latest(ptApiRegistry, plScreenLogI);
gptProfile = pl_get_api_latest(ptApiRegistry, plProfileI);
gptStats = pl_get_api_latest(ptApiRegistry, plStatsI);
gptMemory = pl_get_api_latest(ptApiRegistry, plMemoryI);
gptLog = pl_get_api_latest(ptApiRegistry, plLogI);
gptConsole = pl_get_api_latest(ptApiRegistry, plConsoleI);
gptExample = pl_get_api_latest(ptApiRegistry, plExampleI);
return ptAppData;
}
// retrieve extension registry // retrieve extension registry
const plExtensionRegistryI* ptExtensionRegistry = pl_get_api_latest(ptApiRegistry, plExtensionRegistryI); const plExtensionRegistryI* ptExtensionRegistry = pl_get_api_latest(ptApiRegistry, plExtensionRegistryI);
// load extensions // load extensions
// * first argument is the shared library name WITHOUT the extension
// * second & third argument is the load/unload functions names (use NULL for the default of "pl_load_ext" &
// "pl_unload_ext")
// * fourth argument indicates if the extension is reloadable (should we check for changes and reload if changed)
ptExtensionRegistry->add_path("../../pl-template/out"); ptExtensionRegistry->add_path("../../pl-template/out");
ptExtensionRegistry->load("pl_unity_ext", NULL, NULL, false); ptExtensionRegistry->load("pl_unity_ext", NULL, NULL, true);
ptExtensionRegistry->load("pl_platform_ext", NULL, NULL, false); // provides the file API used by the drawing ext ptExtensionRegistry->load("pl_platform_ext", NULL, NULL, false);
ptExtensionRegistry->load("pl_example_ext", NULL, NULL, true);
// load required apis // load required apis
gptIO = pl_get_api_latest(ptApiRegistry, plIOI); gptIO = pl_get_api_latest(ptApiRegistry, plIOI);
gptWindows = pl_get_api_latest(ptApiRegistry, plWindowI); gptWindows = pl_get_api_latest(ptApiRegistry, plWindowI);
// load required apis (these are provided though extensions) // load required apis (these are provided though extensions)
gptDraw = pl_get_api_latest(ptApiRegistry, plDrawI); gptGfx = pl_get_api_latest(ptApiRegistry, plGraphicsI);
gptShader = pl_get_api_latest(ptApiRegistry, plShaderI);
gptStarter = pl_get_api_latest(ptApiRegistry, plStarterI); gptStarter = pl_get_api_latest(ptApiRegistry, plStarterI);
gptUI = pl_get_api_latest(ptApiRegistry, plUiI); gptImage = pl_get_api_latest(ptApiRegistry, plImageI);
gptVfs = pl_get_api_latest(ptApiRegistry, plVfsI);
gptScreenLog = pl_get_api_latest(ptApiRegistry, plScreenLogI); gptScreenLog = pl_get_api_latest(ptApiRegistry, plScreenLogI);
gptProfile = pl_get_api_latest(ptApiRegistry, plProfileI);
gptStats = pl_get_api_latest(ptApiRegistry, plStatsI);
gptMemory = pl_get_api_latest(ptApiRegistry, plMemoryI);
gptLog = pl_get_api_latest(ptApiRegistry, plLogI);
gptConsole = pl_get_api_latest(ptApiRegistry, plConsoleI);
// out extension
gptExample = pl_get_api_latest(ptApiRegistry, plExampleI);
gptExample->print_to_console("Hello from example extension");
// this path is taken only during first load, so we
// allocate app memory here
ptAppData = PL_ALLOC(sizeof(plAppData));
memset(ptAppData, 0, sizeof(plAppData));
// default values
ptAppData->bShowHelpWindow = true;
// use window API to create a window // use window API to create a window
plWindowDesc tWindowDesc = { plWindowDesc tWindowDesc = {
.pcTitle = "Template App", .pcTitle = "Vulkan Example",
.iXPos = 200, .iXPos = 200,
.iYPos = 200, .iYPos = 200,
.uWidth = 600, .uWidth = 600,
@ -207,21 +107,27 @@ pl_app_load(plApiRegistryI* ptApiRegistry, plAppData* ptAppData)
gptWindows->create(tWindowDesc, &ptAppData->ptWindow); gptWindows->create(tWindowDesc, &ptAppData->ptWindow);
gptWindows->show(ptAppData->ptWindow); gptWindows->show(ptAppData->ptWindow);
// initialize the starter API (handles alot of boilerplate) plGraphicsInit tGraphicsDesc = {
plStarterInit tStarterInit = { .tFlags = PL_GRAPHICS_INIT_FLAGS_SWAPCHAIN_ENABLED | PL_GRAPHICS_INIT_FLAGS_VALIDATION_ENABLED
.tFlags = PL_STARTER_FLAGS_ALL_EXTENSIONS,
.ptWindow = ptAppData->ptWindow
}; };
gptStarter->initialize(tStarterInit); gptGfx->initialize(&tGraphicsDesc);
gptStarter->finalize();
// add a log channel ptAppData->ptSurface = gptGfx->create_surface(ptAppData->ptWindow);
ptAppData->uExampleLogChannel = gptLog->add_channel("Example 2", (plLogExtChannelInit){.tType = PL_LOG_CHANNEL_TYPE_BUFFER});
// add a console variable ptAppData->ptDevice = gptStarter->create_device(ptAppData->ptSurface);
gptConsole->add_toggle_variable("a.HelpWindow", &ptAppData->bShowHelpWindow, "toggle help window", PL_CONSOLE_VARIABLE_FLAGS_NONE);
// initialize shader extension (we are doing this ourselves so we can add additional shader directories)
// return app memory static const plShaderOptions tDefaultShaderOptions = {
.apcIncludeDirectories = {
"../../pl-template/shaders/"
},
.apcDirectories = {
"../shaders/",
"../../pl-template/shaders/"
},
.tFlags = PL_SHADER_FLAGS_AUTO_OUTPUT | PL_SHADER_FLAGS_ALWAYS_COMPILE
};
gptShader->initialize(&tDefaultShaderOptions);
return ptAppData; return ptAppData;
} }
@ -232,9 +138,14 @@ pl_app_load(plApiRegistryI* ptApiRegistry, plAppData* ptAppData)
PL_EXPORT void PL_EXPORT void
pl_app_shutdown(plAppData* ptAppData) pl_app_shutdown(plAppData* ptAppData)
{ {
gptStarter->cleanup(); gptGfx->flush_device(ptAppData->ptDevice);
gptShader->cleanup();
gptWindows->destroy(ptAppData->ptWindow); gptWindows->destroy(ptAppData->ptWindow);
PL_FREE(ptAppData); gptScreenLog->cleanup();
gptGfx->cleanup_surface(ptAppData->ptSurface);
gptGfx->cleanup_device(ptAppData->ptDevice);
gptGfx->cleanup();
free(ptAppData);
} }
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
@ -244,7 +155,7 @@ pl_app_shutdown(plAppData* ptAppData)
PL_EXPORT void PL_EXPORT void
pl_app_resize(plAppData* ptAppData) pl_app_resize(plAppData* ptAppData)
{ {
gptStarter->resize();
} }
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
@ -254,88 +165,322 @@ pl_app_resize(plAppData* ptAppData)
PL_EXPORT void PL_EXPORT void
pl_app_update(plAppData* ptAppData) pl_app_update(plAppData* ptAppData)
{ {
// this needs to be the first call when using the starter gptIO->new_frame(); // must be called once at the beginning of a frame
// extension. You must return if it returns false (usually a swapchain recreation).
if(!gptStarter->begin_frame())
return;
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~stats API~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ plIO* ptIO = gptIO->get_io();
ptIO->bRunning = false;
// rather than have to lookup the counter every frame, its best to "store" it
// like this. To update it, just deference it and set the value.
static double* pdExample2Counter = NULL;
if(!pdExample2Counter)
pdExample2Counter = gptStats->get_counter("example 2 counter");
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~drawing & profile API~~~~~~~~~~~~~~~~~~~~~~~~~~~ // core (basically every vulkan application will have these)
gptProfile->begin_sample(0, "example drawing"); // global resources (for simplicity, we are treating these as global resources)
VkDescriptorPool gtDescriptorPool; // pool of descriptors
plDrawLayer2D* ptFGLayer = gptStarter->get_foreground_layer(); VkCommandPool gtComputeCommandPool; // pool of command buffers
gptDraw->add_line(ptFGLayer, VkFence gtComputeWorkFence; // fence to signal compute work is done (not needed for this example but required by API)
(plVec2){0.0f, 0.0f},
(plVec2){500.0f, 500.0f}, (plDrawLineOptions){ .fThickness = 1.0f, .uColor = PL_COLOR_32_MAGENTA});
plDrawLayer2D* ptBGLayer = gptStarter->get_background_layer(); // example specific resources
gptDraw->add_triangle_filled(ptBGLayer, VkBuffer gtStorageBuffer; // storage buffer (reading & writing for this example)
(plVec2){50.0f, 100.0f}, VkDeviceMemory gtStorageBufferMemory; // storage buffer actual device memory
(plVec2){200.0f}, VkDescriptorSet gtDescriptorSet; // descriptor set which will contain a descriptor to the storage buffer
(plVec2){100.0f, 200.0f}, (plDrawSolidOptions){.uColor = PL_COLOR_32_RGBA(0.0f, 0.5f, 1.0f, 0.5f)}); float* gpfMemoryMap; // persistant mapping to the storage buffer
gptProfile->end_sample(0); // example specific compute pipeline stuff
VkDescriptorSetLayout gtDescriptorSetLayout; // describes the compute shader descriptor set layout (single set in this example)
VkPipelineLayout gtPipelineLayout; // describes the compute shader pipeline layout (descriptor set layout + other stuff in general)
VkPipeline gtPipeline; // complete pipeline state object for compute shader (this really is the "shader")
VkShaderModule gtShaderModule; // SPIR-V code
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~UI & Screen Log API~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ VkDevice gtLogicalDevice = gptGfx->get_vulkan_device(ptAppData->ptDevice);
// creating a window plShaderModule tShader = gptShader->load_glsl("shader.comp", "main", NULL, NULL);
if(ptAppData->bShowHelpWindow)
//-----------------------------------------------------------------------------
// load an image
//-----------------------------------------------------------------------------
size_t szImageFileSize = gptVfs->get_file_size_str("../../pl-template/data/test.hdr");
unsigned char* pucBuffer = malloc(szImageFileSize);
plVfsFileHandle tEnvMapHandle = gptVfs->open_file("../../pl-template/data/test.hdr", PL_VFS_FILE_MODE_READ);
gptVfs->read_file(tEnvMapHandle, pucBuffer, &szImageFileSize);
gptVfs->close_file(tEnvMapHandle);
int iImageWidth = 0;
int iImageHeight = 0;
int iImageChannels = 0;
float* pfRawImageBytes = gptImage->load_hdr(pucBuffer, (int)szImageFileSize, &iImageWidth, &iImageHeight, &iImageChannels, 4);
assert(pfRawImageBytes);
free(pucBuffer);
//-----------------------------------------------------------------------------
// create storage buffer
// - we are using a storage buffer for this example to read & write from
// - we are also storing it in host visible memory for simplicity
//-----------------------------------------------------------------------------
// create storage buffer
VkBufferCreateInfo tBufferInfo = {
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
.size = iImageWidth * iImageHeight * 4 * sizeof(float),
.usage = VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT,
.sharingMode = VK_SHARING_MODE_EXCLUSIVE
};
VULKAN_CHECK(vkCreateBuffer(gtLogicalDevice, &tBufferInfo, NULL, &gtStorageBuffer));
// get buffer memory requirements
VkMemoryRequirements tMemRequirements = {0};
vkGetBufferMemoryRequirements(gtLogicalDevice, gtStorageBuffer, &tMemRequirements);
// allocate actual device memory
VkMemoryAllocateInfo tStorageAllocInfo = {
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
.allocationSize = tMemRequirements.size,
};
// find suitable memory type
VkPhysicalDeviceMemoryProperties gtMemProps = gptGfx->get_vulkan_memory_properties(ptAppData->ptDevice);
for (uint32_t i = 0; i < gtMemProps.memoryTypeCount; i++)
{ {
if(gptUI->begin_window("Help", NULL, PL_UI_WINDOW_FLAGS_AUTO_SIZE | PL_UI_WINDOW_FLAGS_NO_COLLAPSE)) if ((tMemRequirements.memoryTypeBits & (1 << i)) && (gtMemProps.memoryTypes[i].propertyFlags & (VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT)) == (VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT))
{ {
gptUI->layout_static(0.0f, 500.0f, 1); tStorageAllocInfo.memoryTypeIndex = i;
gptUI->text("Press F1 to bring up console."); break;
gptUI->text("Look for t.StatsTool (we added a stat)");
gptUI->text("Look for t.LogTool (we added a log channel)");
gptUI->text("Look for t.ProfileTool");
gptUI->text("Look for t.MemoryAllocationTool and look for example 2!");
gptUI->text("Look for a.HelpWindow (console variable we added)");
gptUI->end_window();
} }
} }
VULKAN_CHECK(vkAllocateMemory(gtLogicalDevice, &tStorageAllocInfo, NULL, &gtStorageBufferMemory));
// creating another window // bind memory to resource
if(gptUI->begin_window("Pilot Light", NULL, PL_UI_WINDOW_FLAGS_NONE)) VULKAN_CHECK(vkBindBufferMemory(gtLogicalDevice, gtStorageBuffer, gtStorageBufferMemory, 0));
{
gptUI->text("Pilot Light %s", PILOT_LIGHT_VERSION_STRING);
if(gptUI->button("Log")) //-----------------------------------------------------------------------------
{ // map to the memory persistantly
gptLog->trace(ptAppData->uExampleLogChannel, "Log"); // - this is really just to make the example simpler
gptLog->debug(ptAppData->uExampleLogChannel, "Log"); // - more generally, you would need to transfer the data & syncronize access
gptLog->info(ptAppData->uExampleLogChannel, "Log"); //-----------------------------------------------------------------------------
gptLog->warn(ptAppData->uExampleLogChannel, "Log");
gptLog->error(ptAppData->uExampleLogChannel, "Log");
gptLog->fatal(ptAppData->uExampleLogChannel, "Log");
}
static int iCounter = 0; VULKAN_CHECK(vkMapMemory(gtLogicalDevice, gtStorageBufferMemory, 0, iImageWidth * iImageHeight * 4 * sizeof(float), 0, (void**)&gpfMemoryMap));
gptUI->slider_int("Stat Counter Example", &iCounter, -10, 10, 0); memcpy(gpfMemoryMap, pfRawImageBytes, iImageWidth * iImageHeight * 4 * sizeof(float));
*pdExample2Counter = iCounter; // setting our stat variable gptImage->free(pfRawImageBytes);
gptUI->layout_row_begin(PL_UI_LAYOUT_ROW_TYPE_DYNAMIC, 0.0f, 2); // got to pl_ui_ext.h to see layout systems //-----------------------------------------------------------------------------
// create descriptor pool, layout, & set
gptUI->layout_row_push(0.3f); //-----------------------------------------------------------------------------
if(gptUI->button("Log To Screen"))
gptScreenLog->add_message(5.0, "Cool Message!");
gptUI->layout_row_push(0.3f); // create descriptor pool
if(gptUI->button("Big Log To Screen")) VkDescriptorPoolSize tPoolSize = {VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 100 };
gptScreenLog->add_message_ex(0, 5, PL_COLOR_32_GREEN, 3.0f, "%s", "Bigger & Greener!"); const VkDescriptorPoolCreateInfo tDescriptorPoolInfo = {
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO,
.flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT,
.maxSets = 100,
.poolSizeCount = 1,
.pPoolSizes = &tPoolSize,
};
VULKAN_CHECK(vkCreateDescriptorPool(gtLogicalDevice, &tDescriptorPoolInfo, NULL, &gtDescriptorPool));
gptUI->layout_row_end(); // create descriptor set layout
const VkDescriptorSetLayoutBinding tBindings = {
.binding = 0u,
.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
.descriptorCount = 1,
.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT
};
gptUI->end_window(); const VkDescriptorSetLayoutCreateInfo tLayoutInfo = {
} .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
.bindingCount = 1,
.pBindings = &tBindings
};
VULKAN_CHECK(vkCreateDescriptorSetLayout(gtLogicalDevice, &tLayoutInfo, NULL, &gtDescriptorSetLayout));
// must be the last function called when using the starter extension // allocate descriptor set from descriptor pool
gptStarter->end_frame(); const VkDescriptorSetAllocateInfo tAllocInfo = {
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO,
.descriptorPool = gtDescriptorPool,
.descriptorSetCount = 1,
.pSetLayouts = &gtDescriptorSetLayout
};
VULKAN_CHECK(vkAllocateDescriptorSets(gtLogicalDevice, &tAllocInfo, &gtDescriptorSet));
//-----------------------------------------------------------------------------
// write data to descriptors
// - you have already created the storage buffer, created the descriptor set,
// but you haven't "connected" them, which is what you are doing here
//-----------------------------------------------------------------------------
// descriptor info
VkDescriptorBufferInfo tBufferDescriptorInfo = {
.buffer = gtStorageBuffer,
.range = VK_WHOLE_SIZE
};
VkWriteDescriptorSet tDescriptorWrites = {
.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
.dstBinding = 0u,
.dstArrayElement = 0,
.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
.descriptorCount = 1,
.dstSet = gtDescriptorSet,
.pBufferInfo = &tBufferDescriptorInfo
};
vkUpdateDescriptorSets(gtLogicalDevice, 1, &tDescriptorWrites, 0, NULL);
//-----------------------------------------------------------------------------
// create shader & pipeline & pipeline layout
//-----------------------------------------------------------------------------
// create pipeline layout (resource bindings)
VkPipelineLayoutCreateInfo tPipelineLayoutInfo = {
.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
.setLayoutCount = 1,
.pSetLayouts = &gtDescriptorSetLayout
};
VULKAN_CHECK(vkCreatePipelineLayout(gtLogicalDevice, &tPipelineLayoutInfo, NULL, &gtPipelineLayout));
VkShaderModuleCreateInfo tShaderCreateInfo = {
.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
.codeSize = tShader.szCodeSize,
.pCode = (const uint32_t*)(tShader.puCode)
};
VULKAN_CHECK(vkCreateShaderModule(gtLogicalDevice, &tShaderCreateInfo, NULL, &gtShaderModule));
VkPipelineShaderStageCreateInfo tShaderStage = {
.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
.stage = VK_SHADER_STAGE_COMPUTE_BIT,
.module = gtShaderModule,
.pName = "main"
};
// create compute pipeline (FYI, this is when actual shader compilation is performed by the driver)
VkComputePipelineCreateInfo tPipelineCreateInfo = {
.sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO,
.layout = gtPipelineLayout,
.stage = tShaderStage
};
VULKAN_CHECK(vkCreateComputePipelines(gtLogicalDevice, VK_NULL_HANDLE, 1, &tPipelineCreateInfo, NULL, &gtPipeline));
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// WORK
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// create command pool
//-----------------------------------------------------------------------------
// create command pool from compute queue
const VkCommandPoolCreateInfo tCommandPoolInfo = {
.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
.queueFamilyIndex = gptGfx->get_vulkan_queue_family(ptAppData->ptDevice),
.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT
};
VULKAN_CHECK(vkCreateCommandPool(gtLogicalDevice, &tCommandPoolInfo, NULL, &gtComputeCommandPool));
VkCommandBufferAllocateInfo tCommandAllocInfo = {
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
.commandPool = gtComputeCommandPool,
.commandBufferCount = 1,
};
//-----------------------------------------------------------------------------
// allocate a command buffer from the command pool
//-----------------------------------------------------------------------------
VkCommandBuffer tCommandBuffer = {0};
vkAllocateCommandBuffers(gtLogicalDevice, &tCommandAllocInfo, &tCommandBuffer);
//-----------------------------------------------------------------------------
// begin recording work into the command buffer
//-----------------------------------------------------------------------------
VkCommandBufferBeginInfo tCommandBufferBeginInfo = {
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT,
};
VULKAN_CHECK(vkBeginCommandBuffer(tCommandBuffer, &tCommandBufferBeginInfo));
// bind compute pipeline
vkCmdBindPipeline(tCommandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, gtPipeline);
// bind resources (remember this is done in sets)
vkCmdBindDescriptorSets(tCommandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, gtPipelineLayout, 0, 1, &gtDescriptorSet, 0, 0);
// dispatch work
// vkCmdDispatch(tCommandBuffer, iImageWidth, iImageHeight, 1);
vkCmdDispatch(tCommandBuffer, iImageWidth, iImageHeight, 1);
// finish recording
VULKAN_CHECK(vkEndCommandBuffer(tCommandBuffer));
// submit command buffer to compute queue
const VkPipelineStageFlags tWaitStageMask = VK_PIPELINE_STAGE_TRANSFER_BIT;
const VkSubmitInfo tComputeSubmitInfo = {
.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
.pWaitDstStageMask = &tWaitStageMask,
.commandBufferCount = 1,
.pCommandBuffers = &tCommandBuffer
};
//-----------------------------------------------------------------------------
// create syncronization primitives
// - not really needed for this example but is required when submitting work
// I believe.
//-----------------------------------------------------------------------------
// fence for compute command buffer sync
const VkFenceCreateInfo tFenceCreateInfo = {
.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO,
.flags = VK_FENCE_CREATE_SIGNALED_BIT
};
VULKAN_CHECK(vkCreateFence(gtLogicalDevice, &tFenceCreateInfo, NULL, &gtComputeWorkFence));
vkResetFences(gtLogicalDevice, 1, &gtComputeWorkFence);
VULKAN_CHECK(vkQueueSubmit(gptGfx->get_vulkan_queue(ptAppData->ptDevice), 1, &tComputeSubmitInfo, gtComputeWorkFence));
// wait for fence to be signaled
VULKAN_CHECK(vkWaitForFences(gtLogicalDevice, 1, &gtComputeWorkFence, VK_TRUE, UINT64_MAX));
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// RESULTS
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
// can only output non-normalized unfortunately
unsigned char* pucResults = malloc(iImageHeight * iImageWidth * 4);
for(uint32_t i = 0; i < (uint32_t)(iImageHeight * iImageWidth * 4); i++)
pucResults[i] = (unsigned char)gpfMemoryMap[i];
plImageWriteInfo tWriteInfo = {
.iWidth = iImageWidth,
.iHeight = iImageHeight,
.iComponents = 4,
.iByteStride = iImageWidth * 4
};
gptImage->write("../../pl-template/out/test_image_results.png", pucResults, &tWriteInfo);
free(pucResults);
// unmap memory & cleanup storage buffer
vkUnmapMemory(gtLogicalDevice, gtStorageBufferMemory);
vkDestroyBuffer(gtLogicalDevice, gtStorageBuffer, NULL);
vkFreeMemory(gtLogicalDevice, gtStorageBufferMemory, NULL);
// cleanup pipeline & shader
vkDestroyShaderModule(gtLogicalDevice, gtShaderModule, NULL);
vkDestroyPipelineLayout(gtLogicalDevice, gtPipelineLayout, NULL);
vkDestroyPipeline(gtLogicalDevice, gtPipeline, NULL);
// cleanup some global resources
vkDestroyCommandPool(gtLogicalDevice, gtComputeCommandPool, NULL);
vkDestroyDescriptorSetLayout(gtLogicalDevice, gtDescriptorSetLayout, NULL);
vkDestroyDescriptorPool(gtLogicalDevice, gtDescriptorPool, NULL);
vkDestroyFence(gtLogicalDevice, gtComputeWorkFence, NULL);
} }