Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
7fcc166
Put all lights into a single array
mrzyanide May 3, 2022
96a14e8
Rename DYNLIGHT_xxx to LIGHT_xxx
res2k Jun 6, 2025
d86862e
Remove now-unused DynLightData
res2k Jun 6, 2025
79c5485
Keep a map of previous frame light IDs
mrzyanide May 3, 2022
68f4bce
ReSTIR FTW
mrzyanide May 3, 2022
a8f10aa
Denoising working, resampling stabilized
mrzyanide Sep 13, 2022
208a6ea
Ported some smaller formatting tweaks, menu changes from NVIDIA#475.
res2k Jun 28, 2025
4004d66
Only reuse previous reservoir if it's similar in position
res2k Jul 10, 2025
d7e9ba7
Remove unused Reservoir member
res2k Jul 10, 2025
d7183f2
Remove some unused variables
res2k Mar 12, 2026
32a9eb9
Comment & reorder 'Reservoir' structure
res2k Feb 7, 2026
604585d
Add a constant for M clamp choice
res2k Mar 14, 2026
8ed1b3d
Remove 'Normalize reservoir', w_sum isn't needed after that point
res2k Mar 1, 2026
e791389
Remove 'bounce' argument from get_direct_illumination_restir().
res2k Mar 14, 2026
674fa17
Split checkerboard↔flat coord conversion functions to separate file
res2k Mar 8, 2026
7228eb3
Sample across checkerboard fields in ReSTIR spatial sampling
res2k Mar 8, 2026
7c48514
Add initial samples to reservoir even if 0
res2k Apr 3, 2026
f253ad6
Make M clamp configurable
res2k Apr 10, 2026
bdb3ec0
Lower M clamp, add comment
res2k Apr 10, 2026
94a12ab
Cap spatial sample influence
res2k Mar 26, 2026
be8b569
For ReSTIR, add sunlight on top of 'other' direct lighting as well
res2k Jan 30, 2026
a73b8b9
Perform actual sampling for ReSTIR unshadowed contrib
res2k Mar 15, 2026
697ab9f
Reject spatial samples that would end up out of bounds
res2k Apr 11, 2026
b63c41a
Remove ReSTIR light weight clamp
res2k Mar 27, 2026
05a3b25
Track & use light stats for ReSTIR as well
res2k Feb 7, 2026
dd197d3
Remove unused direct lighting is_odd_checkboard argument
res2k Mar 8, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions baseq2/q2rtx.menu
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ begin video
toggle "AMD FSR 1.0" flt_fsr_enable
range --status "lower is sharper" "AMD FSR 1.0 sharpness" flt_fsr_sharpness 0 2 0.01
pairs "global illumination" pt_num_bounce_rays low 0.5 medium 1 high 2
pairs "direct light sampling" pt_restir "RIS light" 0 "ReSTIR, high quality" 1 "ReSTIR, half" 2 "ReSTIR, quarter" 3
pairs "reflection/refraction depth" pt_reflect_refract off 0 1 1 2 2 4 4 8 8
toggle --status "turn some monitors in the game into security camera views" \
"security cameras" pt_cameras
Expand Down
2 changes: 2 additions & 0 deletions cmake/compileShaders.cmake
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
set(SHADER_SOURCE_DEPENDENCIES
${CMAKE_SOURCE_DIR}/src/refresh/vkpt/shader/asvgf.glsl
${CMAKE_SOURCE_DIR}/src/refresh/vkpt/shader/brdf.glsl
${CMAKE_SOURCE_DIR}/src/refresh/vkpt/shader/checkerboard.glsl
${CMAKE_SOURCE_DIR}/src/refresh/vkpt/shader/constants.h
${CMAKE_SOURCE_DIR}/src/refresh/vkpt/shader/fsr_easu.glsl
${CMAKE_SOURCE_DIR}/src/refresh/vkpt/shader/fsr_rcas.glsl
Expand All @@ -16,6 +17,7 @@ set(SHADER_SOURCE_DEPENDENCIES
${CMAKE_SOURCE_DIR}/src/refresh/vkpt/shader/precomputed_sky.glsl
${CMAKE_SOURCE_DIR}/src/refresh/vkpt/shader/precomputed_sky_params.h
${CMAKE_SOURCE_DIR}/src/refresh/vkpt/shader/projection.glsl
${CMAKE_SOURCE_DIR}/src/refresh/vkpt/shader/restir.h
${CMAKE_SOURCE_DIR}/src/refresh/vkpt/shader/sky.h
${CMAKE_SOURCE_DIR}/src/refresh/vkpt/shader/tiny_encryption_algorithm.h
${CMAKE_SOURCE_DIR}/src/refresh/vkpt/shader/tone_mapping_utils.glsl
Expand Down
7 changes: 7 additions & 0 deletions doc/client.md
Original file line number Diff line number Diff line change
Expand Up @@ -937,6 +937,13 @@ Selects the projection to use for rendering. Default value is 0.
#### `pt_reflect_refract`
Number of reflection or refraction bounces to trace. Default value is 2.

#### `pt_restir`
Switch for experimental direct light sampling algorithms. Default value is 1.
- 0 — RIS light sampling.
- 1 — ReSTIR, high quality.
- 2 — ReSTIR El-Cheapo, uses half of the shadow rays.
- 3 — ReSTIR El-Very-Cheapo, uses one quarter of the shadow rays.

#### `pt_roughness_override`
Global override for roughness of all materials. Negative values mean there is no
override. Default value is -1.
Expand Down
1 change: 1 addition & 0 deletions inc/refresh/models.h
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ typedef struct light_poly_s {
int cluster;
int style;
float emissive_factor;
int type;
} light_poly_t;

typedef struct maliasmesh_s maliasmesh_t;
Expand Down
4 changes: 4 additions & 0 deletions src/refresh/vkpt/bsp_mesh.c
Original file line number Diff line number Diff line change
Expand Up @@ -959,6 +959,7 @@ collect_one_light_poly_entire_texture(bsp_t *bsp, mface_t *surf, mtexinfo_t *tex

light.material = texinfo->material;
light.style = light_style;
light.type = LIGHT_POLYGON;

if(!get_triangle_off_center(light.positions, light.off_center, NULL, 1.f))
continue;
Expand Down Expand Up @@ -1113,6 +1114,7 @@ collect_one_light_poly(bsp_t *bsp, mface_t *surf, mtexinfo_t *texinfo, int model
light_poly_t* light = append_light_poly(num_lights, allocated_lights, lights);
light->material = texinfo->material;
light->style = light_style;
light->type = LIGHT_POLYGON;
light->emissive_factor = emissive_factor;
VectorCopy(instance_positions[0], light->positions + 0);
VectorCopy(instance_positions[i1], light->positions + 3);
Expand Down Expand Up @@ -1328,6 +1330,7 @@ collect_sky_and_lava_light_polys(bsp_mesh_t *wm, bsp_t* bsp)
}

light.style = 0;
light.type = LIGHT_POLYGON;

if (!get_triangle_off_center(light.positions, light.off_center, NULL, 1.f))
continue;
Expand Down Expand Up @@ -1821,6 +1824,7 @@ bsp_mesh_create_custom_sky_prims(uint32_t* prim_ctr, bsp_mesh_t* wm, const bsp_t
light->material = 0;
light->style = 0;
light->cluster = cluster;
light->type = LIGHT_POLYGON;

++*prim_ctr;

Expand Down
10 changes: 10 additions & 0 deletions src/refresh/vkpt/conversion.h
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,16 @@ static inline float halfToFloat(uint16_t value)
return v.f;
}

static inline float uintBitsToFloat(uint32_t ui)
{
union {
float f;
uint32_t ui;
} bits;
bits.ui = ui;
return bits.f;
}

void packHalf4x16(uint32_t* half, float* vec4);

#endif // CONVERSION_H_
151 changes: 109 additions & 42 deletions src/refresh/vkpt/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ cvar_t *cvar_pt_bilerp_chars = NULL;
cvar_t *cvar_pt_bilerp_pics = NULL;
cvar_t *cvar_pt_waterwarp = NULL;
cvar_t *cvar_pt_ref_bounce_rays = NULL;
cvar_t *cvar_pt_restir_m_clamp = NULL;
cvar_t *cvar_drs_enable = NULL;
cvar_t *cvar_drs_target = NULL;
cvar_t *cvar_drs_minscale = NULL;
Expand All @@ -87,6 +88,7 @@ cvar_t *cvar_ui_hdr_nits = NULL; /* HDR mode UI (stretch pic) brightness in nits
extern cvar_t *scr_viewsize;
extern cvar_t *cvar_bloom_enable;
extern cvar_t* cvar_flt_taa;
extern cvar_t *cvar_pt_restir;
static int drs_current_scale = 0;
static int drs_effective_scale = 0;
static bool drs_last_frame_world = false;
Expand Down Expand Up @@ -1667,20 +1669,14 @@ destroy_vulkan(void)
return 0;
}

typedef struct entity_hash_s {
unsigned int mesh : 8;
unsigned int model : 9;
unsigned int entity : 14;
unsigned int bsp : 1;
} entity_hash_t;

static int entity_frame_num = 0;
static uint32_t model_entity_ids[2][MAX_MODEL_INSTANCES];
static int model_entity_id_count[2];
static int light_entity_ids[2][MAX_MODEL_LIGHTS];
static int light_entity_id_count[2];
static int iqm_matrix_count[2];
static ModelInstance model_instances_prev[MAX_MODEL_INSTANCES];

#define MAX_MODEL_LIGHTS 16384
static int num_model_lights = 0;
static light_poly_t model_lights[MAX_MODEL_LIGHTS];

Expand Down Expand Up @@ -1809,47 +1805,70 @@ static void fill_model_instance(ModelInstance* instance, const entity_t* entity,
instance->material |= MATERIAL_FLAG_LIGHT;
}

static void add_dlight_spot(const dlight_t* light, DynLightData* dynlight_data)
static void add_dlight_spot(const dlight_t* dlight, light_poly_t* light)
{
// Copy spot data
VectorCopy(light->spot.direction, dynlight_data->spot_direction);
switch(light->spot.emission_profile)
VectorCopy(dlight->spot.direction, light->positions + 6);
uint spot_type = 0, spot_data = 0;
switch(dlight->spot.emission_profile)
{
case DLIGHT_SPOT_EMISSION_PROFILE_FALLOFF:
dynlight_data->type |= DYNLIGHT_SPOT_EMISSION_PROFILE_FALLOFF << 16;
dynlight_data->spot_data = floatToHalf(light->spot.cos_total_width) | (floatToHalf(light->spot.cos_falloff_start) << 16);
spot_type = DYNLIGHT_SPOT_EMISSION_PROFILE_FALLOFF;
spot_data = floatToHalf(dlight->spot.cos_total_width) | (floatToHalf(dlight->spot.cos_falloff_start) << 16);
break;
case DLIGHT_SPOT_EMISSION_PROFILE_AXIS_ANGLE_TEXTURE:
dynlight_data->type |= DYNLIGHT_SPOT_EMISSION_PROFILE_AXIS_ANGLE_TEXTURE << 16;
dynlight_data->spot_data = floatToHalf(light->spot.total_width) | (light->spot.texture << 16);
spot_type = DYNLIGHT_SPOT_EMISSION_PROFILE_AXIS_ANGLE_TEXTURE;
spot_data = floatToHalf(dlight->spot.total_width) | (dlight->spot.texture << 16);
break;
}
light->positions[4] = uintBitsToFloat(spot_type);
light->positions[5] = uintBitsToFloat(spot_data);
}

static void
add_dlights(const dlight_t* lights, int num_lights, QVKUniformBuffer_t* ubo)
add_dlights(const dlight_t* dlights, int num_dlights, light_poly_t* light_list, int* num_lights, int max_lights, bsp_t *bsp, int* light_entity_ids)
{
ubo->num_dyn_lights = 0;

for (int i = 0; i < num_lights; i++)
for (int i = 0; i < num_dlights; i++)
{
const dlight_t* light = lights + i;

DynLightData* dynlight_data = ubo->dyn_light_data + ubo->num_dyn_lights;
VectorCopy(light->origin, dynlight_data->center);
VectorScale(light->color, light->intensity / 25.f, dynlight_data->color);
dynlight_data->radius = light->radius;
switch(light->light_type) {
case DLIGHT_SPHERE:
dynlight_data->type = DYNLIGHT_SPHERE;
break;
case DLIGHT_SPOT:
dynlight_data->type = DYNLIGHT_SPOT;
add_dlight_spot(light, dynlight_data);
break;
}
if (*num_lights >= max_lights)
return;

const dlight_t* dlight = dlights + i;
light_poly_t* light = light_list + *num_lights;

light->cluster = BSP_PointLeaf(bsp->nodes, dlight->origin)->cluster;

entity_hash_t hash;
hash.entity = i + 1; //entity ID
hash.mesh = 0xAA;

ubo->num_dyn_lights++;
if(light->cluster >= 0)
{
//Super wasteful but we want to have all lights in the same list.

VectorCopy(dlight->origin, light->positions + 0);
VectorScale(dlight->color, dlight->intensity / 25.f, light->color);
light->positions[3] = dlight->radius;
light->material = NULL;
light->style = 0;

switch(dlight->light_type) {
case DLIGHT_SPHERE:
light->type = LIGHT_SPHERE;
hash.model = 0xFE;
break;
case DLIGHT_SPOT:
light->type = LIGHT_SPOT;
// Copy spot data
add_dlight_spot(dlight, light);
hash.model = 0xFD;
break;
}

light_entity_ids[(*num_lights)] = *(uint32_t*)&hash;
(*num_lights)++;

}
}
}

Expand All @@ -1861,7 +1880,7 @@ static inline void transform_point(const float* p, const float* matrix, float* r
VectorCopy(transformed, result); // vec4 -> vec3
}

static void instance_model_lights(int num_light_polys, const light_poly_t* light_polys, const float* transform)
static void instance_model_lights(int num_light_polys, const light_poly_t* light_polys, const float* transform, entity_hash_t hash)
{
for (int nlight = 0; nlight < num_light_polys; nlight++)
{
Expand Down Expand Up @@ -1891,10 +1910,15 @@ static void instance_model_lights(int num_light_polys, const light_poly_t* light
VectorCopy(src_light->color, dst_light->color);
dst_light->material = src_light->material;
dst_light->style = src_light->style;
dst_light->type = LIGHT_POLYGON;

hash.mesh = nlight; //More a light index than a mesh
light_entity_ids[entity_frame_num][num_model_lights] = *(uint32_t*)&hash;

num_model_lights++;
}
}

static const mat4 g_identity_transform = {
{ 1.f, 0.f, 0.f, 0.f },
{ 0.f, 1.f, 0.f, 0.f },
Expand Down Expand Up @@ -1974,7 +1998,7 @@ static void process_bsp_entity(const entity_t* entity, int* instance_count)
mi->render_buffer_idx = VERTEX_BUFFER_WORLD;
mi->render_prim_offset = model->geometry.prim_offsets[0];

instance_model_lights(model->num_light_polys, model->light_polys, transform);
instance_model_lights(model->num_light_polys, model->light_polys, transform, hash);

if (model->geometry.accel)
{
Expand Down Expand Up @@ -2167,7 +2191,13 @@ static void process_regular_entity(
mult_matrix_vector(end, transform, offset2);
VectorSet(color, 0.25f, 0.5f, 0.07f);

vkpt_build_cylinder_light(model_lights, &num_model_lights, MAX_MODEL_LIGHTS, bsp_world_model, begin, end, color, 1.5f);
entity_hash_t hash;
hash.entity = entity->id;
hash.model = entity->model;
hash.mesh = 0;
hash.bsp = 0;

vkpt_build_cylinder_light(model_lights, &num_model_lights, MAX_MODEL_LIGHTS, bsp_world_model, begin, end, color, 1.5f, hash, light_entity_ids[entity_frame_num]);
}

*instance_count = current_instance_index;
Expand Down Expand Up @@ -2242,7 +2272,13 @@ prepare_entities(EntityUploadInfo* upload_info)
else
create_entity_matrix(transform, (entity_t*)entity);

instance_model_lights(model->num_light_polys, model->light_polys, transform);
entity_hash_t hash;
hash.entity = i + 1;
hash.model = ~entity->model;
hash.mesh = 0;
hash.bsp = 0;

instance_model_lights(model->num_light_polys, model->light_polys, transform, hash);
}
}
}
Expand Down Expand Up @@ -2319,6 +2355,7 @@ prepare_entities(EntityUploadInfo* upload_info)

memset(instance_buffer->model_current_to_prev, -1, sizeof(instance_buffer->model_current_to_prev));
memset(instance_buffer->model_prev_to_current, -1, sizeof(instance_buffer->model_prev_to_current));
memset(instance_buffer->mlight_prev_to_current, ~0u, sizeof(instance_buffer->mlight_prev_to_current));

model_entity_id_count[entity_frame_num] = model_instance_idx;
for(int i = 0; i < model_entity_id_count[entity_frame_num]; i++) {
Expand Down Expand Up @@ -2804,6 +2841,14 @@ prepare_ubo(refdef_t *fd, mleaf_t* viewleaf, const reference_mode_t* ref_mode, c
qvk.surf_is_hdr ? (cvar_ui_hdr_nits->value * 0.0125) // an scRGB luminance of 1.0 is defined as 80 nits
: 1.f /* no change */;

ubo->restir_m_clamp = cvar_pt_restir_m_clamp->integer;
if (cvar_pt_restir->integer == 3)
{
// adjust clamp for "very cheap" mode
ubo->restir_m_clamp /= 2;
}
ubo->restir_m_clamp = max(ubo->restir_m_clamp, 1); // don't break things

qvk.extent_render_prev = qvk.extent_render;
qvk.gpu_slice_width_prev = qvk.gpu_slice_width;

Expand Down Expand Up @@ -2938,8 +2983,6 @@ prepare_ubo(refdef_t *fd, mleaf_t* viewleaf, const reference_mode_t* ref_mode, c
VectorCopy(sky_matrix[0], ubo->environment_rotation_matrix[0]);
VectorCopy(sky_matrix[1], ubo->environment_rotation_matrix[1]);
VectorCopy(sky_matrix[2], ubo->environment_rotation_matrix[2]);

add_dlights(vkpt_refdef.fd->dlights, vkpt_refdef.fd->num_dlights, ubo);

if (wm->num_cameras > 0)
{
Expand All @@ -2956,6 +2999,21 @@ prepare_ubo(refdef_t *fd, mleaf_t* viewleaf, const reference_mode_t* ref_mode, c
ubo->num_cameras = wm->num_cameras;
}

static void
update_mlight_prev_to_current(void)
{
light_entity_id_count[entity_frame_num] = num_model_lights;
for(int i = 0; i < light_entity_id_count[entity_frame_num]; i++) {
entity_hash_t hash = *(entity_hash_t*)&light_entity_ids[entity_frame_num][i];
if(hash.entity == 0u) continue;
for(int j = 0; j < light_entity_id_count[!entity_frame_num]; j++) {
if(light_entity_ids[entity_frame_num][i] == light_entity_ids[!entity_frame_num][j]) {
vkpt_refdef.uniform_instance_buffer.mlight_prev_to_current[j] = i;
break;
}
}
}
}

/* renders the map ingame */
void
Expand Down Expand Up @@ -3044,9 +3102,12 @@ R_RenderFrame_RTX(refdef_t *fd)
vkpt_pt_instance_model_blas(&vkpt_refdef.bsp_mesh_world.geom_sky, g_identity_transform, VERTEX_BUFFER_WORLD, -1, 0);
vkpt_pt_instance_model_blas(&vkpt_refdef.bsp_mesh_world.geom_custom_sky, g_identity_transform, VERTEX_BUFFER_WORLD, -1, 0);

vkpt_build_beam_lights(model_lights, &num_model_lights, MAX_MODEL_LIGHTS, bsp_world_model, fd->entities, fd->num_entities, prev_adapted_luminance);
vkpt_build_beam_lights(model_lights, &num_model_lights, MAX_MODEL_LIGHTS, bsp_world_model, fd->entities, fd->num_entities, prev_adapted_luminance, light_entity_ids[entity_frame_num], &num_model_lights);
add_dlights(vkpt_refdef.fd->dlights, vkpt_refdef.fd->num_dlights, model_lights, &num_model_lights, MAX_MODEL_LIGHTS, bsp_world_model, light_entity_ids[entity_frame_num]);
}

update_mlight_prev_to_current();

vkpt_vertex_buffer_ensure_primbuf_size(upload_info.num_prims);

QVKUniformBuffer_t *ubo = &vkpt_refdef.uniform_buffer;
Expand Down Expand Up @@ -3842,6 +3903,12 @@ R_Init_RTX(bool total)
// useful to eg compare direct lighting only with reference.
cvar_pt_ref_bounce_rays = Cvar_Get("pt_ref_bounce_rays", "2", 0);

// ReSTIR M clamp value
/* Note: Higher values results in pixel having higher correlation between frames;
* however, this can work against the denoiser, as it's temporal filtering would
* really likes pixels that vary over time... */
cvar_pt_restir_m_clamp = Cvar_Get("pt_restir_m_clamp", "8", 0);

#ifdef VKPT_DEVICE_GROUPS
cvar_sli = Cvar_Get("sli", "1", CVAR_REFRESH | CVAR_ARCHIVE);
#endif
Expand Down
6 changes: 6 additions & 0 deletions src/refresh/vkpt/path_tracer.c
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ cvar_t* cvar_pt_enable_sprites = NULL;

extern cvar_t *cvar_pt_caustics;
extern cvar_t *cvar_pt_reflect_refract;
extern cvar_t *cvar_pt_restir;


typedef struct QvkGeometryInstance_s {
Expand Down Expand Up @@ -1142,6 +1143,11 @@ vkpt_pt_trace_lighting(VkCommandBuffer cmd_buf, float num_bounce_rays)
BARRIER_COMPUTE(cmd_buf, qvk.images[VKPT_IMG_PT_COLOR_HF]);
BARRIER_COMPUTE(cmd_buf, qvk.images[VKPT_IMG_PT_COLOR_SPEC]);

if(cvar_pt_restir->value != 0) {
int frame_idx = qvk.frame_counter & 1;
BARRIER_COMPUTE(cmd_buf, qvk.images[VKPT_IMG_PT_RESTIR_A + frame_idx]);
}

BUFFER_BARRIER(cmd_buf,
.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT,
.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT,
Expand Down
Loading
Loading