rt: draft a universal pass sketch

allows easily creating arbitrary ray tracing pipelines out of sets of
bindings and shaders

what's missing:
- optimal handling of duplicate shaders
- reading bindings from shaders (parse SPIR-V) instead of specifying in
  code
- automatic resource creation according to binding definitions
- automatic barriers according to bindings interface
This commit is contained in:
Ivan Avdeev 2022-01-26 23:52:14 -08:00
parent e384f2e1f7
commit b3c69851dc
7 changed files with 300 additions and 128 deletions

177
ref_vk/ray_pass.c Normal file
View File

@ -0,0 +1,177 @@
#include "ray_pass.h"
#include "vk_descriptor.h"
#include "vk_ray_resources.h"
#include "vk_ray_resources.h"
#define MAX_STAGES 16
#define MAX_MISS_GROUPS 8
#define MAX_HIT_GROUPS 8
typedef struct ray_pass_s {
// TODO enum type
struct {
vk_descriptors_t riptors;
ray_pass_write_values_f write_values_func;
VkDescriptorSet sets[1];
} desc;
union {
vk_pipeline_ray_t tracing;
};
} ray_pass_t;
#if 0 // TODO
qboolean createLayout( const ray_pass_layout_t *layout, ray_pass_t *pass ){
// TODO return false on fail instead of crashing
{
const VkDescriptorSetLayoutCreateInfo dslci = {
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
.bindingCount = layout->bindings_count,
.pBindings = layout->bindings,
};
XVK_CHECK(vkCreateDescriptorSetLayout(vk_core.device, &dslci, NULL, &pass->desc_set_layout));
}
{
const VkPipelineLayoutCreateInfo plci = {
.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
.setLayoutCount = 1,
.pSetLayouts = &pass->desc_set_layout,
.pushConstantRangeCount = layout->push_constants.size > 0 ? 1 : 0,
.pPushConstantRanges = &layout->push_constants,
};
XVK_CHECK(vkCreatePipelineLayout(vk_core.device, &plci, NULL, &pass->pipeline_layout));
}
return true;
}
#endif
static ray_pass_t *createRayPass( const ray_pass_create_t *create ) {
ray_pass_t *pass = Mem_Malloc(vk_core.pool, sizeof(*pass));
{
pass->desc.riptors = (vk_descriptors_t) {
.bindings = create->layout.bindings,
.num_bindings = create->layout.bindings_count,
.num_sets = COUNTOF(pass->desc.sets),
.desc_sets = pass->desc.sets,
.push_constants = create->layout.push_constants,
};
VK_DescriptorsCreate(&pass->desc.riptors);
}
{
int stage_index = 0;
vk_shader_stage_t stages[MAX_STAGES];
int miss_index = 0;
int misses[MAX_MISS_GROUPS];
int hit_index = 0;
vk_pipeline_ray_hit_group_t hits[MAX_HIT_GROUPS];
vk_pipeline_ray_create_info_t prci = {
.debug_name = create->debug_name,
.layout = pass->desc.riptors.pipeline_layout,
.stages = stages,
.groups = {
.hit = hits,
.miss = misses,
},
};
stages[stage_index++] = (vk_shader_stage_t) {
.filename = create->tracing.raygen,
.stage = VK_SHADER_STAGE_RAYGEN_BIT_KHR,
.specialization_info = create->tracing.specialization,
};
for (int i = 0; i < create->tracing.miss_count; ++i) {
const ray_pass_shader_t *const shader = create->tracing.miss + i;
ASSERT(stage_index < MAX_STAGES);
ASSERT(miss_index < MAX_MISS_GROUPS);
// TODO handle duplicate filenames
misses[miss_index++] = stage_index;
stages[stage_index++] = (vk_shader_stage_t) {
.filename = *shader,
.stage = VK_SHADER_STAGE_MISS_BIT_KHR,
.specialization_info = create->tracing.specialization,
};
}
for (int i = 0; i < create->tracing.hit_count; ++i) {
const ray_pass_hit_group_t *const group = create->tracing.hit + i;
ASSERT(hit_index < MAX_HIT_GROUPS);
// TODO handle duplicate filenames
if (group->any) {
ASSERT(stage_index < MAX_STAGES);
hits[hit_index].any = stage_index;
stages[stage_index++] = (vk_shader_stage_t) {
.filename = group->any,
.stage = VK_SHADER_STAGE_ANY_HIT_BIT_KHR,
.specialization_info = create->tracing.specialization,
};
} else {
hits[hit_index].any = -1;
}
if (group->closest) {
ASSERT(stage_index < MAX_STAGES);
hits[hit_index].closest = stage_index;
stages[stage_index++] = (vk_shader_stage_t) {
.filename = group->closest,
.stage = VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR,
.specialization_info = create->tracing.specialization,
};
} else {
hits[hit_index].closest = -1;
}
++hit_index;
}
prci.groups.hit_count = hit_index;
prci.groups.miss_count = miss_index;
prci.stages_count = stage_index;
pass->tracing = VK_PipelineRayTracingCreate(&prci);
}
if (pass->tracing.pipeline == VK_NULL_HANDLE) {
VK_DescriptorsDestroy(&pass->desc.riptors);
Mem_Free(pass);
return NULL;
}
pass->desc.riptors.values = Mem_Malloc(vk_core.pool, sizeof(pass->desc.riptors.values[0]) * create->layout.bindings_count);
pass->desc.write_values_func = create->layout.write_values_func;
ASSERT(create->layout.write_values_func);
return pass;
}
struct ray_pass_s *RayPassCreate( const ray_pass_create_t *create ) {
return createRayPass(create);
}
void RayPassDestroy( struct ray_pass_s *pass ) {
VK_PipelineRayTracingDestroy(&pass->tracing);
VK_DescriptorsDestroy(&pass->desc.riptors);
Mem_Free(pass->desc.riptors.values);
Mem_Free(pass);
}
void RayPassPerform( VkCommandBuffer cmdbuf, struct ray_pass_s *pass, const struct vk_ray_resources_s *res) {
pass->desc.write_values_func( pass->desc.riptors.values, res );
VK_DescriptorsWrite(&pass->desc.riptors);
vkCmdBindPipeline(cmdbuf, VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR, pass->tracing.pipeline);
vkCmdBindDescriptorSets(cmdbuf, VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR, pass->desc.riptors.pipeline_layout, 0, 1, pass->desc.riptors.desc_sets + 0, 0, NULL);
VK_PipelineRayTracingTrace(cmdbuf, &pass->tracing, res->width, res->height);
}

65
ref_vk/ray_pass.h Normal file
View File

@ -0,0 +1,65 @@
#pragma once
#include "vk_core.h"
#include "vk_pipeline.h"
#include "vk_descriptor.h"
typedef const char* ray_pass_shader_t;
typedef struct {
ray_pass_shader_t closest;
ray_pass_shader_t any;
} ray_pass_hit_group_t;
typedef struct {
ray_pass_shader_t raygen;
const ray_pass_shader_t *miss;
int miss_count;
const ray_pass_hit_group_t *hit;
int hit_count;
const VkSpecializationInfo *specialization;
} ray_pass_tracing_t;
// enum {
// RVkRayPassType_Compute,
// RVkRayPassType_Tracing,
// };
// TODO these should be like:
// - parse the entire layout from shaders
// - expose it as a struct[] interface of the pass
// - resource/interface should prepare descriptors outside of pass code and just pass them to pass
struct vk_ray_resources_s;
typedef void (*ray_pass_write_values_f)( vk_descriptor_value_t *values, const struct vk_ray_resources_s *resources );
// TODO parse this out from shaders
typedef struct {
VkDescriptorSetLayoutBinding *bindings;
int bindings_count;
VkPushConstantRange push_constants;
ray_pass_write_values_f write_values_func;
} ray_pass_layout_t;
typedef struct {
// TODO enum type
const char *debug_name;
ray_pass_layout_t layout;
union {
ray_pass_tracing_t tracing;
};
} ray_pass_create_t;
struct ray_pass_s;
struct ray_pass_s *RayPassCreate( const ray_pass_create_t *create );
void RayPassDestroy( struct ray_pass_s *pass );
struct vk_ray_resources_s;
void RayPassPerform( VkCommandBuffer cmdbuf, struct ray_pass_s *pass, const struct vk_ray_resources_s *res);

View File

@ -1,10 +1,12 @@
#pragma once
#include "vk_core.h"
#include "vk_buffer.h"
typedef struct {
const char *filename;
VkShaderStageFlagBits stage;
VkSpecializationInfo *specialization_info;
const VkSpecializationInfo *specialization_info;
} vk_shader_stage_t;
typedef struct {

View File

@ -2,10 +2,8 @@
#include "vk_ray_internal.h"
#include "vk_descriptor.h"
#include "vk_pipeline.h"
#include "vk_buffer.h"
#include "eiface.h" // ARRAYSIZE
#include "vk_ray_resources.h"
#include "ray_pass.h"
enum {
// TODO set 0
@ -24,35 +22,11 @@ RAY_PRIMARY_OUTPUTS(X)
RtPrim_Desc_COUNT
};
static struct {
struct {
vk_descriptors_t riptors;
VkDescriptorSetLayoutBinding bindings[RtPrim_Desc_COUNT];
vk_descriptor_value_t values[RtPrim_Desc_COUNT];
// TODO: split into two sets, one common to all rt passes (tlas, kusochki, etc), another one this pass only
VkDescriptorSet sets[1];
} desc;
vk_pipeline_ray_t pipeline;
} g_ray_primary;
static VkDescriptorSetLayoutBinding bindings[RtPrim_Desc_COUNT];
static void initDescriptors( void ) {
g_ray_primary.desc.riptors = (vk_descriptors_t) {
.bindings = g_ray_primary.desc.bindings,
.num_bindings = ARRAYSIZE(g_ray_primary.desc.bindings),
.values = g_ray_primary.desc.values,
.num_sets = ARRAYSIZE(g_ray_primary.desc.sets),
.desc_sets = g_ray_primary.desc.sets,
/* .push_constants = (VkPushConstantRange){ */
/* .offset = 0, */
/* .size = sizeof(vk_rtx_push_constants_t), */
/* .stageFlags = VK_SHADER_STAGE_RAYGEN_BIT_KHR | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_ANY_HIT_BIT_KHR, */
/* }, */
};
#define INIT_BINDING(index, name, type, count, stages) \
g_ray_primary.desc.bindings[RtPrim_Desc_##name] = (VkDescriptorSetLayoutBinding){ \
bindings[RtPrim_Desc_##name] = (VkDescriptorSetLayoutBinding){ \
.binding = index, \
.descriptorType = type, \
.descriptorCount = count, \
@ -71,13 +45,11 @@ static void initDescriptors( void ) {
RAY_PRIMARY_OUTPUTS(X)
#undef X
#undef INIT_BINDING
VK_DescriptorsCreate(&g_ray_primary.desc.riptors);
}
static void updateDescriptors( const vk_ray_resources_t* res ) {
static void writeValues( vk_descriptor_value_t *values, const vk_ray_resources_t* res ) {
#define X(index, name, ...) \
g_ray_primary.desc.values[RtPrim_Desc_Out_##name].image = (VkDescriptorImageInfo){ \
values[RtPrim_Desc_Out_##name].image = (VkDescriptorImageInfo){ \
.sampler = VK_NULL_HANDLE, \
.imageView = res->name, \
.imageLayout = VK_IMAGE_LAYOUT_GENERAL, \
@ -85,14 +57,14 @@ static void updateDescriptors( const vk_ray_resources_t* res ) {
RAY_PRIMARY_OUTPUTS(X)
#undef X
g_ray_primary.desc.values[RtPrim_Desc_TLAS].accel = (VkWriteDescriptorSetAccelerationStructureKHR){
values[RtPrim_Desc_TLAS].accel = (VkWriteDescriptorSetAccelerationStructureKHR){
.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET_ACCELERATION_STRUCTURE_KHR,
.accelerationStructureCount = 1,
.pAccelerationStructures = &res->scene.tlas,
};
#define DESC_SET_BUFFER(index, buffer_) \
g_ray_primary.desc.values[index].buffer = (VkDescriptorBufferInfo){ \
values[index].buffer = (VkDescriptorBufferInfo){ \
.buffer = res->scene.buffer_.buffer, \
.offset = res->scene.buffer_.offset, \
.range = res->scene.buffer_.size, \
@ -105,13 +77,11 @@ RAY_PRIMARY_OUTPUTS(X)
#undef DESC_SET_BUFFER
g_ray_primary.desc.values[RtPrim_Desc_Textures].image_array = res->scene.all_textures;
VK_DescriptorsWrite(&g_ray_primary.desc.riptors);
values[RtPrim_Desc_Textures].image_array = res->scene.all_textures;
}
static vk_pipeline_ray_t createPipeline( void ) {
// FIXME move this into vk_pipeline
struct ray_pass_s *R_VkRayPrimaryPassCreate( void ) {
// FIXME move this into vk_pipeline or something
const struct SpecializationData {
uint32_t sbt_record_size;
} spec_data = {
@ -120,94 +90,44 @@ static vk_pipeline_ray_t createPipeline( void ) {
const VkSpecializationMapEntry spec_map[] = {
{.constantID = SPEC_SBT_RECORD_SIZE_INDEX, .offset = offsetof(struct SpecializationData, sbt_record_size), .size = sizeof(uint32_t) },
};
VkSpecializationInfo spec = {
const VkSpecializationInfo spec = {
.mapEntryCount = COUNTOF(spec_map),
.pMapEntries = spec_map,
.dataSize = sizeof(spec_data),
.pData = &spec_data,
};
#define LIST_SHADER_MODULES(X) \
X(RayGen, "ray_primary.rgen", RAYGEN) \
X(Miss, "ray_primary.rmiss", MISS) \
X(HitClosest, "ray_primary.rchit", CLOSEST_HIT) \
X(HitAnyAlphaTest, "ray_common_alphatest.rahit", ANY_HIT) \
enum {
#define X(name, file, type) \
ShaderStageIndex_##name,
LIST_SHADER_MODULES(X)
#undef X
const ray_pass_shader_t miss[] = {
"ray_primary.rmiss.spv"
};
const vk_shader_stage_t stages[] = {
#define X(name, file, type) \
{.filename = file ".spv", .stage = VK_SHADER_STAGE_##type##_BIT_KHR, .specialization_info = &spec},
LIST_SHADER_MODULES(X)
#undef X
};
const int misses[] = {
ShaderStageIndex_Miss,
};
const vk_pipeline_ray_hit_group_t hits[] = {
// TODO rigidly specify the expected sbt structure w/ offsets and materials
{ // 0: fully opaque: no need for closest nor any hits
.closest = ShaderStageIndex_HitClosest,
.any = -1,
},
{ // 1: materials w/ alpha mask: need alpha test
.closest = ShaderStageIndex_HitClosest,
.any = ShaderStageIndex_HitAnyAlphaTest, // TODO these can directly be a string
const ray_pass_hit_group_t hit[] = { {
.closest = "ray_primary.rchit.spv",
.any = NULL,
}, {
.closest = "ray_primary.rchit.spv",
.any = "ray_common_alphatest.rahit.spv",
},
};
const vk_pipeline_ray_create_info_t prtc = {
const ray_pass_create_t rpc = {
.debug_name = "primary ray",
.stages = stages,
.stages_count = COUNTOF(stages),
.groups = {
.miss = misses,
.miss_count = COUNTOF(misses),
.hit = hits,
.hit_count = COUNTOF(hits),
.layout = {
.bindings = bindings,
.bindings_count = COUNTOF(bindings),
.write_values_func = writeValues,
.push_constants = {0},
},
.tracing = {
.raygen = "ray_primary.rgen.spv",
.miss = miss,
.miss_count = COUNTOF(miss),
.hit = hit,
.hit_count = COUNTOF(hit),
.specialization = &spec,
},
.layout = g_ray_primary.desc.riptors.pipeline_layout,
};
return VK_PipelineRayTracingCreate(&prtc);
}
qboolean XVK_RayTracePrimaryInit( void ) {
initDescriptors();
g_ray_primary.pipeline = createPipeline();
ASSERT(g_ray_primary.pipeline.pipeline != VK_NULL_HANDLE);
return true;
return RayPassCreate( &rpc );
}
void XVK_RayTracePrimaryDestroy( void ) {
VK_PipelineRayTracingDestroy(&g_ray_primary.pipeline);
VK_DescriptorsDestroy(&g_ray_primary.desc.riptors);
}
void XVK_RayTracePrimaryReloadPipeline( void ) {
const vk_pipeline_ray_t new_pipeline = createPipeline();
if (new_pipeline.pipeline == VK_NULL_HANDLE)
return;
VK_PipelineRayTracingDestroy(&g_ray_primary.pipeline);
g_ray_primary.pipeline = new_pipeline;
}
void XVK_RayTracePrimary( VkCommandBuffer cmdbuf, const vk_ray_resources_t *res ) {
updateDescriptors( res );
vkCmdBindPipeline(cmdbuf, VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR, g_ray_primary.pipeline.pipeline);
vkCmdBindDescriptorSets(cmdbuf, VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR, g_ray_primary.desc.riptors.pipeline_layout, 0, 1, g_ray_primary.desc.riptors.desc_sets + 0, 0, NULL);
VK_PipelineRayTracingTrace(cmdbuf, &g_ray_primary.pipeline, res->width, res->height);
}

View File

@ -1,9 +1,3 @@
#pragma once
#include "vk_ray_resources.h"
qboolean XVK_RayTracePrimaryInit( void );
void XVK_RayTracePrimaryDestroy( void );
void XVK_RayTracePrimaryReloadPipeline( void );
void XVK_RayTracePrimary( VkCommandBuffer cmdbuf, const vk_ray_resources_t *res );
struct ray_pass_s *R_VkRayPrimaryPassCreate( void );

View File

@ -4,7 +4,7 @@
#include "vk_const.h"
#include "shaders/ray_interop.h"
typedef struct {
typedef struct vk_ray_resources_s {
uint32_t width, height;
struct {

View File

@ -1,5 +1,6 @@
#include "vk_rtx.h"
#include "ray_pass.h"
#include "vk_ray_primary.h"
#include "vk_ray_light_direct.h"
@ -162,6 +163,10 @@ static struct {
unsigned frame_number;
xvk_ray_frame_images_t frames[MAX_FRAMES_IN_FLIGHT];
struct {
struct ray_pass_s *primary_ray;
} pass;
qboolean reload_pipeline;
qboolean reload_lighting;
} g_rtx = {0};
@ -1075,7 +1080,7 @@ static void performTracing( VkCommandBuffer cmdbuf, const vk_ray_frame_render_ar
0, 0, NULL, ARRAYSIZE(bmb), bmb, ARRAYSIZE(image_barrier), image_barrier);
}
XVK_RayTracePrimary( cmdbuf, &res );
RayPassPerform( cmdbuf, g_rtx.pass.primary_ray, &res );
{
const VkImageMemoryBarrier image_barriers[] = {
@ -1158,7 +1163,14 @@ void VK_RayFrameEnd(const vk_ray_frame_render_args_t* args)
//vkDestroyPipeline(vk_core.device, g_rtx.pipeline, NULL);
createPipeline();
XVK_RayTracePrimaryReloadPipeline();
{
struct ray_pass_s *new_primary_pass = R_VkRayPrimaryPassCreate();
if (new_primary_pass) {
RayPassDestroy(g_rtx.pass.primary_ray);
g_rtx.pass.primary_ray = new_primary_pass;
}
}
XVK_RayTraceLightDirectReloadPipeline();
XVK_DenoiserReloadPipeline();
g_rtx.reload_pipeline = false;
@ -1329,7 +1341,9 @@ qboolean VK_RayInit( void )
ASSERT(vk_core.rtx);
// TODO complain and cleanup on failure
ASSERT(XVK_RayTracePrimaryInit());
g_rtx.pass.primary_ray = R_VkRayPrimaryPassCreate();
ASSERT(g_rtx.pass.primary_ray);
ASSERT(XVK_RayTraceLightDirectInit());
g_rtx.sbt_record_size = vk_core.physical_device.sbt_record_size;
@ -1451,7 +1465,7 @@ void VK_RayShutdown( void ) {
ASSERT(vk_core.rtx);
XVK_RayTraceLightDirectDestroy();
XVK_RayTracePrimaryDestroy();
RayPassDestroy(g_rtx.pass.primary_ray);
for (int i = 0; i < ARRAYSIZE(g_rtx.frames); ++i) {
XVK_ImageDestroy(&g_rtx.frames[i].denoised);