Skip to content

Commit

Permalink
Merged pull request "Keep a small history of 'light counts' for each …
Browse files Browse the repository at this point in the history
…cluster used for sampling lights": #234
  • Loading branch information
apanteleev committed Dec 6, 2022
2 parents 2332a90 + c5a0127 commit 5bc0736
Show file tree
Hide file tree
Showing 7 changed files with 137 additions and 48 deletions.
4 changes: 2 additions & 2 deletions src/refresh/vkpt/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -334,7 +334,7 @@ vkpt_destroy_all(VkptInitFlags_t destroy_flags)
if ((VKPT_INIT_DEFAULT & destroy_flags) == destroy_flags)
{
destroy_transparency();
vkpt_light_stats_destroy();
vkpt_light_buffers_destroy();
}

return VK_SUCCESS;
Expand Down Expand Up @@ -4098,7 +4098,7 @@ R_BeginRegistration_RTX(const char *name)
bsp_world_model = bsp;
bsp_mesh_register_textures(bsp);
bsp_mesh_create_from_bsp(&vkpt_refdef.bsp_mesh_world, bsp, name);
vkpt_light_stats_create(&vkpt_refdef.bsp_mesh_world);
vkpt_light_buffers_create(&vkpt_refdef.bsp_mesh_world);
_VK(vkpt_vertex_buffer_upload_bsp_mesh(&vkpt_refdef.bsp_mesh_world));
vkpt_refdef.bsp_mesh_world_loaded = 1;
bsp = NULL;
Expand Down
20 changes: 13 additions & 7 deletions src/refresh/vkpt/shader/light_lists.h
Original file line number Diff line number Diff line change
Expand Up @@ -153,8 +153,14 @@ sample_polygonal_lights(

uint list_start = light_buffer.light_list_offsets[list_idx];
uint list_end = light_buffer.light_list_offsets[list_idx + 1];

float partitions = ceil(float(list_end - list_start) / float(MAX_BRUTEFORCE_SAMPLING));
/* The light count we base light selection on may differ from the current count
* to avoid gradient estimation breaking (see comment on light_counts_history).
* Obtain the frame number for the historical count from the RNG seed
* (which is also the historical RNG seed) */
uint history_index = (rng_seed >> RNG_SEED_SHIFT_FRAME) % LIGHT_COUNT_HISTORY;
uint light_count = light_counts_history[history_index].sample_light_counts[list_idx];

float partitions = ceil(float(light_count) / float(MAX_BRUTEFORCE_SAMPLING));
rng.x *= partitions;
float fpart = min(floor(rng.x), partitions-1);
rng.x -= fpart;
Expand All @@ -167,17 +173,17 @@ sample_polygonal_lights(

#pragma unroll
for(uint i = 0, n_idx = list_start; i < MAX_BRUTEFORCE_SAMPLING; i++, n_idx += stride) {
if (n_idx >= list_end)
if (n_idx >= list_start + light_count)
break;

uint current_idx = light_buffer.light_list_lights[n_idx];

if(current_idx == ~0u)
if(n_idx >= list_end)
{
light_masses[i] = 0;
continue;
}

uint current_idx = light_buffer.light_list_lights[n_idx];

LightPolygon light = get_light_polygon(current_idx);

float m = projected_tri_area(light.positions, p, n, V, phong_exp, phong_scale, phong_weight);
Expand Down Expand Up @@ -241,7 +247,7 @@ sample_polygonal_lights(

#pragma unroll
for(uint i = 0, n_idx = list_start; i < MAX_BRUTEFORCE_SAMPLING; i++, n_idx += stride) {
if (n_idx >= list_end)
if (n_idx >= list_start + light_count)
break;
pdf = light_masses[i];
current_idx = int(n_idx);
Expand Down
11 changes: 10 additions & 1 deletion src/refresh/vkpt/shader/path_tracer_rgen.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,14 @@ uniform accelerationStructureEXT topLevelAS[TLAS_COUNT];

#define DESATURATE_ENVIRONMENT_MAP 1

/* RNG seeds contain 'X' and 'Y' values that are computed w/ a modulo BLUE_NOISE_RES,
* so the shift values can be chosen to fit BLUE_NOISE_RES - 1
* (see generate_rng_seed()) */
#define RNG_SEED_SHIFT_X 0u
#define RNG_SEED_SHIFT_Y 8u
#define RNG_SEED_SHIFT_ISODD 16u
#define RNG_SEED_SHIFT_FRAME 17u

#define RNG_PRIMARY_OFF_X 0
#define RNG_PRIMARY_OFF_Y 1
#define RNG_PRIMARY_APERTURE_X 2
Expand Down Expand Up @@ -171,7 +179,8 @@ get_hit_barycentric(RayPayloadGeometry rp)
float
get_rng(uint idx)
{
uvec3 p = uvec3(rng_seed, rng_seed >> 10, rng_seed >> 20);
uvec3 p = uvec3(rng_seed >> RNG_SEED_SHIFT_X, rng_seed >> RNG_SEED_SHIFT_Y, rng_seed >> RNG_SEED_SHIFT_ISODD);
p.z = (p.z >> 1) + (p.z & 1);
p.z = (p.z + idx);
p &= uvec3(BLUE_NOISE_RES - 1, BLUE_NOISE_RES - 1, NUM_BLUE_NOISE_TEX - 1);

Expand Down
7 changes: 4 additions & 3 deletions src/refresh/vkpt/shader/primary_rays.rgen
Original file line number Diff line number Diff line change
Expand Up @@ -101,9 +101,10 @@ void generate_rng_seed(ivec2 ipos, bool is_odd_checkerboard)
uint frame_offset = frame_num / NUM_BLUE_NOISE_TEX;

rng_seed = 0;
rng_seed |= (uint(ipos.x + frame_offset) % BLUE_NOISE_RES) << 0u;
rng_seed |= (uint(ipos.y + (frame_offset << 4)) % BLUE_NOISE_RES) << 10u;
rng_seed |= uint(frame_num + uint(is_odd_checkerboard)) << 20;
rng_seed |= (uint(ipos.x + frame_offset) % BLUE_NOISE_RES) << RNG_SEED_SHIFT_X;
rng_seed |= (uint(ipos.y + (frame_offset << 4)) % BLUE_NOISE_RES) << RNG_SEED_SHIFT_Y;
rng_seed |= uint(is_odd_checkerboard) << RNG_SEED_SHIFT_ISODD;
rng_seed |= uint(frame_num) << RNG_SEED_SHIFT_FRAME;

imageStore(IMG_ASVGF_RNG_SEED_A, ipos, uvec4(rng_seed));
}
Expand Down
28 changes: 22 additions & 6 deletions src/refresh/vkpt/shader/vertex_buffer.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,

#define MAX_LIGHT_LISTS (1 << 14)
#define MAX_LIGHT_LIST_NODES (1 << 19)
#define LIGHT_COUNT_HISTORY 16

#define MAX_IQM_MATRICES 32768

Expand All @@ -41,12 +42,13 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#define PRIMITIVE_BUFFER_BINDING_IDX 0
#define POSITION_BUFFER_BINDING_IDX 1
#define LIGHT_BUFFER_BINDING_IDX 2
#define IQM_MATRIX_BUFFER_BINDING_IDX 3
#define READBACK_BUFFER_BINDING_IDX 4
#define TONE_MAPPING_BUFFER_BINDING_IDX 5
#define SUN_COLOR_BUFFER_BINDING_IDX 6
#define SUN_COLOR_UBO_BINDING_IDX 7
#define LIGHT_STATS_BUFFER_BINDING_IDX 8
#define LIGHT_COUNTS_HISTORY_BUFFER_BINDING_IDX 3
#define IQM_MATRIX_BUFFER_BINDING_IDX 4
#define READBACK_BUFFER_BINDING_IDX 5
#define TONE_MAPPING_BUFFER_BINDING_IDX 6
#define SUN_COLOR_BUFFER_BINDING_IDX 7
#define SUN_COLOR_UBO_BINDING_IDX 8
#define LIGHT_STATS_BUFFER_BINDING_IDX 9

#define VERTEX_BUFFER_WORLD 0
#define VERTEX_BUFFER_INSTANCED 1
Expand Down Expand Up @@ -196,6 +198,20 @@ layout(set = VERTEX_BUFFER_DESC_SET_IDX, binding = LIGHT_BUFFER_BINDING_IDX) rea
LightBuffer light_buffer;
};

/* History of light count in cluster, used for sampling.
* This is used to make gradient estimation work correctly:
* "The A-SVGF algorithm uses old random numbers to select lights for a subset of pixels,
* and expects to get the same result if the lighting didn't change."
* (quoted from discussion on GH PR 227).
* One way to achieve this is to also keep a history of light numbers and use that for
* sampling "old" data.
*
* We have multiple buffers b/c we may need to access the history for the current or any previous frame.
* That'd be harder to do with a light_buffer member since that is backed by alternating buffers */
layout(set = VERTEX_BUFFER_DESC_SET_IDX, binding = LIGHT_COUNTS_HISTORY_BUFFER_BINDING_IDX) readonly buffer LIGHT_COUNTS_HISTORY_BUFFER {
uint sample_light_counts[];
} light_counts_history[LIGHT_COUNT_HISTORY];

layout(set = VERTEX_BUFFER_DESC_SET_IDX, binding = IQM_MATRIX_BUFFER_BINDING_IDX) readonly buffer IQM_MATRIX_BUFFER {
IqmMatrixBuffer iqm_matrix_buffer;
};
Expand Down
110 changes: 83 additions & 27 deletions src/refresh/vkpt/vertex_buffer.c
Original file line number Diff line number Diff line change
Expand Up @@ -464,18 +464,34 @@ vkpt_iqm_matrix_buffer_upload_staging(VkCommandBuffer cmd_buf)
static int local_light_counts[MAX_MAP_LEAFS];
static int cluster_light_counts[MAX_MAP_LEAFS];
static int light_list_tails[MAX_MAP_LEAFS];
static int max_cluster_model_lights[MAX_MAP_LEAFS];
static int max_model_lights;

void vkpt_light_buffer_reset_counts()
{
memset(max_cluster_model_lights, 0, sizeof(max_cluster_model_lights));
max_model_lights = 0;
}

void
inject_model_lights(bsp_mesh_t* bsp_mesh, bsp_t* bsp, int num_model_lights, light_poly_t* transformed_model_lights, int model_light_offset, uint32_t* dst_list_offsets, uint32_t* dst_lists)
static void copy_bsp_lights(bsp_mesh_t* bsp_mesh, LightBuffer *lbo)
{
// Copy the BSP light lists verbatim
memcpy(lbo->light_list_lights, bsp_mesh->cluster_lights, sizeof(uint32_t) * bsp_mesh->cluster_light_offsets[bsp_mesh->num_clusters]);
memcpy(lbo->light_list_offsets, bsp_mesh->cluster_light_offsets, sizeof(uint32_t) * (bsp_mesh->num_clusters + 1));
// Store the light counts in the light counts history entry for the current frame
uint history_index = qvk.frame_counter % LIGHT_COUNT_HISTORY;
uint *sample_light_counts = (uint *)buffer_map(qvk.buf_light_counts_history + history_index);
for (int c = 0; c < bsp_mesh->num_clusters; c++)
{
sample_light_counts[c] = bsp_mesh->cluster_light_offsets[c + 1] - bsp_mesh->cluster_light_offsets[c];
}
buffer_unmap(qvk.buf_light_counts_history + history_index);
}

static void
inject_model_lights(bsp_mesh_t* bsp_mesh, bsp_t* bsp, int num_model_lights, light_poly_t* transformed_model_lights, int model_light_offset, LightBuffer *lbo)
{
uint32_t *dst_list_offsets = lbo->light_list_offsets;
uint32_t *dst_lists = lbo->light_list_lights;

memset(local_light_counts, 0, bsp_mesh->num_clusters * sizeof(int));
memset(cluster_light_counts, 0, bsp_mesh->num_clusters * sizeof(int));

Expand Down Expand Up @@ -505,19 +521,12 @@ inject_model_lights(bsp_mesh_t* bsp_mesh, bsp_t* bsp, int num_model_lights, ligh
}
}

// Update the max light counts per cluster

for (int c = 0; c < bsp_mesh->num_clusters; c++)
{
max_cluster_model_lights[c] = max(max_cluster_model_lights[c], cluster_light_counts[c]);
}

// Count the total required list size

int required_size = bsp_mesh->cluster_light_offsets[bsp_mesh->num_clusters];
for (int c = 0; c < bsp_mesh->num_clusters; c++)
{
required_size += max_cluster_model_lights[c];
required_size += cluster_light_counts[c];
}

// See if we have enough room in the interaction buffer
Expand All @@ -527,12 +536,15 @@ inject_model_lights(bsp_mesh_t* bsp_mesh, bsp_t* bsp, int num_model_lights, ligh
Com_WPrintf("Insufficient light interaction buffer size (%d needed). Increase MAX_LIGHT_LIST_NODES.\n", required_size);

// Copy the BSP light lists verbatim
memcpy(dst_lists, bsp_mesh->cluster_lights, sizeof(uint32_t) * bsp_mesh->cluster_light_offsets[bsp_mesh->num_clusters]);
memcpy(dst_list_offsets, bsp_mesh->cluster_light_offsets, sizeof(uint32_t) * (bsp_mesh->num_clusters + 1));

copy_bsp_lights(bsp_mesh, lbo);

return;
}

// Store the light counts in the light counts history entry for the current frame
uint history_index = qvk.frame_counter % LIGHT_COUNT_HISTORY;
uint *sample_light_counts = (uint *)buffer_map(qvk.buf_light_counts_history + history_index);

// Copy the static light lists, and make room in these lists to inject the model lights

int tail = 0;
Expand All @@ -544,16 +556,17 @@ inject_model_lights(bsp_mesh_t* bsp_mesh, bsp_t* bsp, int num_model_lights, ligh
memcpy(dst_lists + tail, bsp_mesh->cluster_lights + bsp_mesh->cluster_light_offsets[c], sizeof(uint32_t) * original_size);
tail += original_size;

assert(tail + max_cluster_model_lights[c] < MAX_LIGHT_LIST_NODES);
assert(tail + cluster_light_counts[c] < MAX_LIGHT_LIST_NODES);

if (max_cluster_model_lights[c] > 0) {
memset(dst_lists + tail, 0xff, sizeof(uint32_t) * max_cluster_model_lights[c]);
}
light_list_tails[c] = tail;
tail += max_cluster_model_lights[c];
tail += cluster_light_counts[c];

sample_light_counts[c] = original_size + cluster_light_counts[c];
}
dst_list_offsets[bsp_mesh->num_clusters] = tail;

buffer_unmap(qvk.buf_light_counts_history + history_index);

// Write the model light indices into the light lists

for (int nlight = 0; nlight < num_model_lights; nlight++)
Expand All @@ -566,12 +579,26 @@ inject_model_lights(bsp_mesh_t* bsp_mesh, bsp_t* bsp, int num_model_lights, ligh
if (mask[j] & (1 << k))
{
int other_cluster = j * 8 + k;
dst_lists[light_list_tails[other_cluster]++] = model_light_offset + nlight;
int list_index = light_list_tails[other_cluster]++;
// assert we're not writing into the space reserved for following cluster
assert(list_index < dst_list_offsets[other_cluster + 1]);
dst_lists[list_index] = model_light_offset + nlight;
}
}
}
}
}

#if defined(_DEBUG)
// Verify tight packing
for (int c = 0; c < bsp_mesh->num_clusters; c++)
{
int list_start = dst_list_offsets[c];
int list_end = dst_list_offsets[c + 1];
int original_size = bsp_mesh->cluster_light_offsets[c + 1] - bsp_mesh->cluster_light_offsets[c];
assert(list_end - list_start == original_size + cluster_light_counts[c]);
}
#endif
}

static inline void
Expand Down Expand Up @@ -639,12 +666,11 @@ vkpt_light_buffer_upload_to_staging(bool render_world, bsp_mesh_t *bsp_mesh, bsp
// If any of the BSP models contain lights, inject these lights right into the visibility lists.
// The shader doesn't know that these lights are dynamic.

inject_model_lights(bsp_mesh, bsp, num_model_lights, transformed_model_lights, model_light_offset, lbo->light_list_offsets, lbo->light_list_lights);
inject_model_lights(bsp_mesh, bsp, num_model_lights, transformed_model_lights, model_light_offset, lbo);
}
else
{
memcpy(lbo->light_list_offsets, bsp_mesh->cluster_light_offsets, (bsp_mesh->num_clusters + 1) * sizeof(uint32_t));
memcpy(lbo->light_list_lights, bsp_mesh->cluster_lights, bsp_mesh->num_cluster_lights * sizeof(uint32_t));
copy_bsp_lights(bsp_mesh, lbo);
}

for (int nlight = 0; nlight < bsp_mesh->num_light_polys; nlight++)
Expand Down Expand Up @@ -1213,6 +1239,12 @@ vkpt_vertex_buffer_create()
.binding = LIGHT_BUFFER_BINDING_IDX,
.stageFlags = VK_SHADER_STAGE_ALL,
},
{
.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
.descriptorCount = LIGHT_COUNT_HISTORY,
.binding = LIGHT_COUNTS_HISTORY_BUFFER_BINDING_IDX,
.stageFlags = VK_SHADER_STAGE_ALL,
},
{
.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
.descriptorCount = 1,
Expand Down Expand Up @@ -1445,9 +1477,9 @@ vkpt_vertex_buffer_destroy()
return VK_SUCCESS;
}

VkResult vkpt_light_stats_create(bsp_mesh_t *bsp_mesh)
VkResult vkpt_light_buffers_create(bsp_mesh_t *bsp_mesh)
{
vkpt_light_stats_destroy();
vkpt_light_buffers_destroy();

// Light statistics: 2 uints (shadowed, unshadowed) per light per surface orientation (6) per cluster.
uint32_t num_stats = bsp_mesh->num_clusters * bsp_mesh->num_light_polys * 6 * 2;
Expand Down Expand Up @@ -1491,16 +1523,40 @@ VkResult vkpt_light_stats_create(bsp_mesh_t *bsp_mesh)

vkUpdateDescriptorSets(qvk.device, 1, &output_buf_write, 0, NULL);

// Set up buffers for light counts history
VkDescriptorBufferInfo light_counts_buf_info[LIGHT_COUNT_HISTORY];
for (int h = 0; h < LIGHT_COUNT_HISTORY; h++)
{
buffer_create(qvk.buf_light_counts_history + h, sizeof(uint32_t) * bsp_mesh->num_clusters,
VK_BUFFER_USAGE_STORAGE_BUFFER_BIT,
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);

light_counts_buf_info[h].buffer = qvk.buf_light_counts_history[h].buffer;
light_counts_buf_info[h].offset = 0;
light_counts_buf_info[h].range = qvk.buf_light_counts_history[h].size;
}

output_buf_write.dstBinding = LIGHT_COUNTS_HISTORY_BUFFER_BINDING_IDX;
output_buf_write.descriptorCount = LENGTH(light_counts_buf_info);
output_buf_write.pBufferInfo = light_counts_buf_info;

vkUpdateDescriptorSets(qvk.device, 1, &output_buf_write, 0, NULL);

return VK_SUCCESS;
}

VkResult vkpt_light_stats_destroy()
VkResult vkpt_light_buffers_destroy()
{
for (int frame = 0; frame < NUM_LIGHT_STATS_BUFFERS; frame++)
{
buffer_destroy(qvk.buf_light_stats + frame);
}

for (int h = 0; h < LIGHT_COUNT_HISTORY; h++)
{
buffer_destroy(qvk.buf_light_counts_history + h);
}

return VK_SUCCESS;
}

Expand Down
5 changes: 3 additions & 2 deletions src/refresh/vkpt/vkpt.h
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,7 @@ typedef struct QVK_s {
BufferResource_t buf_light;
BufferResource_t buf_light_staging[MAX_FRAMES_IN_FLIGHT];
BufferResource_t buf_light_stats[NUM_LIGHT_STATS_BUFFERS];
BufferResource_t buf_light_counts_history[LIGHT_COUNT_HISTORY];

BufferResource_t buf_iqm_matrices;
BufferResource_t buf_iqm_matrices_staging[MAX_FRAMES_IN_FLIGHT];
Expand Down Expand Up @@ -627,8 +628,8 @@ VkResult vkpt_vertex_buffer_upload_models();
void vkpt_light_buffer_reset_counts();
VkResult vkpt_light_buffer_upload_to_staging(bool render_world, bsp_mesh_t *bsp_mesh, bsp_t* bsp, int num_model_lights, light_poly_t* transformed_model_lights, const float* sky_radiance);
VkResult vkpt_light_buffer_upload_staging(VkCommandBuffer cmd_buf);
VkResult vkpt_light_stats_create(bsp_mesh_t *bsp_mesh);
VkResult vkpt_light_stats_destroy();
VkResult vkpt_light_buffers_create(bsp_mesh_t *bsp_mesh);
VkResult vkpt_light_buffers_destroy();
bool vkpt_model_is_static(const model_t* model);
const model_vbo_t* vkpt_get_model_vbo(const model_t* model);

Expand Down

0 comments on commit 5bc0736

Please sign in to comment.