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
git clone https://github.com/PilotLightTech/pilotlight
cd pilotlight/src
chmod +x build_linux.sh
chmod +x build.sh -c moltenvk
./build_linux.sh
# 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"],
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
@ -84,54 +81,6 @@ with pl.project("game"):
# 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
@ -148,35 +97,21 @@ with pl.project("game"):
# win32
with pl.platform("Windows"):
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
with pl.platform("Linux"):
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
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
pl.add_dynamic_link_libraries("vulkan")
#-----------------------------------------------------------------------------
# [SECTION] generate scripts

View File

@ -28,7 +28,8 @@ includes = [
"${workspaceFolder}/../pilotlight/libs",
"${workspaceFolder}/../pilotlight/extensions",
"${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:
// [SECTION] quick notes
@ -22,45 +10,6 @@ Index of this file:
// [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
//-----------------------------------------------------------------------------
@ -73,17 +22,17 @@ Index of this file:
#include "pl_math.h"
// 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_ui_ext.h"
#include "pl_image_ext.h"
#include "pl_vfs_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 "pl_example_ext.h"
#include "vulkan/vulkan.h"
#define VULKAN_CHECK(x) assert(x == VK_SUCCESS)
//-----------------------------------------------------------------------------
// [SECTION] structs
@ -93,12 +42,12 @@ typedef struct _plAppData
{
// window
plWindow* ptWindow;
plSurface* ptSurface;
plDevice* ptDevice;
// log channel
uint64_t uExampleLogChannel;
// shaders
plShaderHandle tShader;
// console variable
bool bShowHelpWindow;
} plAppData;
//-----------------------------------------------------------------------------
@ -107,20 +56,12 @@ typedef struct _plAppData
const plIOI* gptIO = NULL;
const plWindowI* gptWindows = NULL;
const plDrawI* gptDraw = NULL;
const plGraphicsI* gptGfx = NULL;
const plShaderI* gptShader = NULL;
const plStarterI* gptStarter = NULL;
const plUiI* gptUI = NULL;
const plImageI* gptImage = NULL;
const plVfsI* gptVfs = 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
@ -129,76 +70,35 @@ const plExampleI* gptExample = NULL;
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
// 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
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;
}
// this path is taken only during first load, so we
// allocate app memory here
ptAppData = malloc(sizeof(plAppData));
memset(ptAppData, 0, sizeof(plAppData));
// retrieve extension registry
const plExtensionRegistryI* ptExtensionRegistry = pl_get_api_latest(ptApiRegistry, plExtensionRegistryI);
// 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->load("pl_unity_ext", NULL, NULL, false);
ptExtensionRegistry->load("pl_platform_ext", NULL, NULL, false); // provides the file API used by the drawing ext
ptExtensionRegistry->load("pl_example_ext", NULL, NULL, true);
ptExtensionRegistry->load("pl_unity_ext", NULL, NULL, true);
ptExtensionRegistry->load("pl_platform_ext", NULL, NULL, false);
// load required apis
gptIO = pl_get_api_latest(ptApiRegistry, plIOI);
gptWindows = pl_get_api_latest(ptApiRegistry, plWindowI);
// 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);
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);
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
plWindowDesc tWindowDesc = {
.pcTitle = "Template App",
.pcTitle = "Vulkan Example",
.iXPos = 200,
.iYPos = 200,
.uWidth = 600,
@ -207,21 +107,27 @@ pl_app_load(plApiRegistryI* ptApiRegistry, plAppData* ptAppData)
gptWindows->create(tWindowDesc, &ptAppData->ptWindow);
gptWindows->show(ptAppData->ptWindow);
// initialize the starter API (handles alot of boilerplate)
plStarterInit tStarterInit = {
.tFlags = PL_STARTER_FLAGS_ALL_EXTENSIONS,
.ptWindow = ptAppData->ptWindow
plGraphicsInit tGraphicsDesc = {
.tFlags = PL_GRAPHICS_INIT_FLAGS_SWAPCHAIN_ENABLED | PL_GRAPHICS_INIT_FLAGS_VALIDATION_ENABLED
};
gptStarter->initialize(tStarterInit);
gptStarter->finalize();
gptGfx->initialize(&tGraphicsDesc);
// add a log channel
ptAppData->uExampleLogChannel = gptLog->add_channel("Example 2", (plLogExtChannelInit){.tType = PL_LOG_CHANNEL_TYPE_BUFFER});
ptAppData->ptSurface = gptGfx->create_surface(ptAppData->ptWindow);
// add a console variable
gptConsole->add_toggle_variable("a.HelpWindow", &ptAppData->bShowHelpWindow, "toggle help window", PL_CONSOLE_VARIABLE_FLAGS_NONE);
// return app memory
ptAppData->ptDevice = gptStarter->create_device(ptAppData->ptSurface);
// initialize shader extension (we are doing this ourselves so we can add additional shader directories)
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;
}
@ -232,9 +138,14 @@ pl_app_load(plApiRegistryI* ptApiRegistry, plAppData* ptAppData)
PL_EXPORT void
pl_app_shutdown(plAppData* ptAppData)
{
gptStarter->cleanup();
gptGfx->flush_device(ptAppData->ptDevice);
gptShader->cleanup();
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_app_resize(plAppData* ptAppData)
{
gptStarter->resize();
}
//-----------------------------------------------------------------------------
@ -254,88 +165,322 @@ pl_app_resize(plAppData* ptAppData)
PL_EXPORT void
pl_app_update(plAppData* ptAppData)
{
// this needs to be the first call when using the starter
// extension. You must return if it returns false (usually a swapchain recreation).
if(!gptStarter->begin_frame())
return;
gptIO->new_frame(); // must be called once at the beginning of a frame
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~stats API~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// 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");
plIO* ptIO = gptIO->get_io();
ptIO->bRunning = false;
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~drawing & profile API~~~~~~~~~~~~~~~~~~~~~~~~~~~
// core (basically every vulkan application will have these)
gptProfile->begin_sample(0, "example drawing");
plDrawLayer2D* ptFGLayer = gptStarter->get_foreground_layer();
gptDraw->add_line(ptFGLayer,
(plVec2){0.0f, 0.0f},
(plVec2){500.0f, 500.0f}, (plDrawLineOptions){ .fThickness = 1.0f, .uColor = PL_COLOR_32_MAGENTA});
// global resources (for simplicity, we are treating these as global resources)
VkDescriptorPool gtDescriptorPool; // pool of descriptors
VkCommandPool gtComputeCommandPool; // pool of command buffers
VkFence gtComputeWorkFence; // fence to signal compute work is done (not needed for this example but required by API)
plDrawLayer2D* ptBGLayer = gptStarter->get_background_layer();
gptDraw->add_triangle_filled(ptBGLayer,
(plVec2){50.0f, 100.0f},
(plVec2){200.0f},
(plVec2){100.0f, 200.0f}, (plDrawSolidOptions){.uColor = PL_COLOR_32_RGBA(0.0f, 0.5f, 1.0f, 0.5f)});
// example specific resources
VkBuffer gtStorageBuffer; // storage buffer (reading & writing for this example)
VkDeviceMemory gtStorageBufferMemory; // storage buffer actual device memory
VkDescriptorSet gtDescriptorSet; // descriptor set which will contain a descriptor to the storage buffer
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
if(ptAppData->bShowHelpWindow)
plShaderModule tShader = gptShader->load_glsl("shader.comp", "main", NULL, NULL);
//-----------------------------------------------------------------------------
// 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);
gptUI->text("Press F1 to bring up console.");
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();
tStorageAllocInfo.memoryTypeIndex = i;
break;
}
}
VULKAN_CHECK(vkAllocateMemory(gtLogicalDevice, &tStorageAllocInfo, NULL, &gtStorageBufferMemory));
// creating another window
if(gptUI->begin_window("Pilot Light", NULL, PL_UI_WINDOW_FLAGS_NONE))
{
gptUI->text("Pilot Light %s", PILOT_LIGHT_VERSION_STRING);
// bind memory to resource
VULKAN_CHECK(vkBindBufferMemory(gtLogicalDevice, gtStorageBuffer, gtStorageBufferMemory, 0));
if(gptUI->button("Log"))
{
gptLog->trace(ptAppData->uExampleLogChannel, "Log");
gptLog->debug(ptAppData->uExampleLogChannel, "Log");
gptLog->info(ptAppData->uExampleLogChannel, "Log");
gptLog->warn(ptAppData->uExampleLogChannel, "Log");
gptLog->error(ptAppData->uExampleLogChannel, "Log");
gptLog->fatal(ptAppData->uExampleLogChannel, "Log");
}
//-----------------------------------------------------------------------------
// map to the memory persistantly
// - this is really just to make the example simpler
// - more generally, you would need to transfer the data & syncronize access
//-----------------------------------------------------------------------------
static int iCounter = 0;
gptUI->slider_int("Stat Counter Example", &iCounter, -10, 10, 0);
*pdExample2Counter = iCounter; // setting our stat variable
VULKAN_CHECK(vkMapMemory(gtLogicalDevice, gtStorageBufferMemory, 0, iImageWidth * iImageHeight * 4 * sizeof(float), 0, (void**)&gpfMemoryMap));
memcpy(gpfMemoryMap, pfRawImageBytes, iImageWidth * iImageHeight * 4 * sizeof(float));
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
gptUI->layout_row_push(0.3f);
if(gptUI->button("Log To Screen"))
gptScreenLog->add_message(5.0, "Cool Message!");
//-----------------------------------------------------------------------------
// create descriptor pool, layout, & set
//-----------------------------------------------------------------------------
gptUI->layout_row_push(0.3f);
if(gptUI->button("Big Log To Screen"))
gptScreenLog->add_message_ex(0, 5, PL_COLOR_32_GREEN, 3.0f, "%s", "Bigger & Greener!");
// create descriptor pool
VkDescriptorPoolSize tPoolSize = {VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 100 };
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
gptStarter->end_frame();
// allocate descriptor set from descriptor pool
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);
}