diff --git a/inc/refresh/models.h b/inc/refresh/models.h index 54ef18d09..5d7918219 100644 --- a/inc/refresh/models.h +++ b/inc/refresh/models.h @@ -107,6 +107,16 @@ typedef struct iqm_mesh_s uint32_t first_influence, num_influences; } iqm_mesh_t; +typedef struct light_poly_s { + float positions[9]; // 3x vec3_t + vec3_t off_center; + vec3_t color; + struct pbr_material_s* material; + int cluster; + int style; + float emissive_factor; +} light_poly_t; + typedef struct model_s { enum { MOD_FREE, @@ -142,6 +152,9 @@ typedef struct model_s { qboolean sprite_vertical; iqm_model_t* iqmData; + + int num_light_polys; + light_poly_t* light_polys; } model_t; extern model_t r_models[]; diff --git a/src/refresh/vkpt/main.c b/src/refresh/vkpt/main.c index 44869e93a..9576144fb 100644 --- a/src/refresh/vkpt/main.c +++ b/src/refresh/vkpt/main.c @@ -1476,7 +1476,7 @@ static int model_entity_id_count[2]; static int world_entity_id_count[2]; static int iqm_matrix_count[2]; -#define MAX_MODEL_LIGHTS 1024 +#define MAX_MODEL_LIGHTS 16384 static int num_model_lights = 0; static light_poly_t model_lights[MAX_MODEL_LIGHTS]; @@ -1588,6 +1588,40 @@ 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) +{ + for (int nlight = 0; nlight < num_light_polys; nlight++) + { + if (num_model_lights >= MAX_MODEL_LIGHTS) + { + assert(!"Model light count overflow"); + break; + } + + const light_poly_t* src_light = light_polys + nlight; + light_poly_t* dst_light = model_lights + num_model_lights; + + // Transform the light's positions and center + transform_point(src_light->positions + 0, transform, dst_light->positions + 0); + transform_point(src_light->positions + 3, transform, dst_light->positions + 3); + transform_point(src_light->positions + 6, transform, dst_light->positions + 6); + transform_point(src_light->off_center, transform, dst_light->off_center); + + // Find the cluster based on the center. Maybe it's OK to use the model's cluster, need to test. + dst_light->cluster = BSP_PointLeaf(bsp_world_model->nodes, dst_light->off_center)->cluster; + + // We really need to map these lights to a cluster + if (dst_light->cluster < 0) + continue; + + // Copy the other light properties + VectorCopy(src_light->color, dst_light->color); + dst_light->material = src_light->material; + + num_model_lights++; + } +} + static void process_bsp_entity(const entity_t* entity, int* bsp_mesh_idx, int* instance_idx, int* num_instanced_vert) { QVKInstanceBuffer_t* uniform_instance_buffer = &vkpt_refdef.uniform_instance_buffer; @@ -1659,37 +1693,8 @@ static void process_bsp_entity(const entity_t* entity, int* bsp_mesh_idx, int* i ((int*)uniform_instance_buffer->model_indices)[*instance_idx] = ~current_bsp_mesh_index; *num_instanced_vert += mesh_vertex_num; - - for (int nlight = 0; nlight < model->num_light_polys; nlight++) - { - if (num_model_lights >= MAX_MODEL_LIGHTS) - { - assert(!"Model light count overflow"); - break; - } - - const light_poly_t* src_light = model->light_polys + nlight; - light_poly_t* dst_light = model_lights + num_model_lights; - - // Transform the light's positions and center - transform_point(src_light->positions + 0, transform, dst_light->positions + 0); - transform_point(src_light->positions + 3, transform, dst_light->positions + 3); - transform_point(src_light->positions + 6, transform, dst_light->positions + 6); - transform_point(src_light->off_center, transform, dst_light->off_center); - - // Find the cluster based on the center. Maybe it's OK to use the model's cluster, need to test. - dst_light->cluster = BSP_PointLeaf(bsp_world_model->nodes, dst_light->off_center)->cluster; - - // We really need to map these lights to a cluster - if(dst_light->cluster < 0) - continue; - - // Copy the other light properties - VectorCopy(src_light->color, dst_light->color); - dst_light->material = src_light->material; - - num_model_lights++; - } + + instance_model_lights(model->num_light_polys, model->light_polys, transform); (*bsp_mesh_idx)++; (*instance_idx)++; @@ -1937,6 +1942,15 @@ prepare_entities(EntityUploadInfo* upload_info) if (contains_masked) masked_model_indices[masked_model_num++] = i; } + + if (model->num_light_polys > 0) + { + float transform[16]; + const qboolean is_viewer_weapon = (entity->flags & RF_WEAPONMODEL) != 0; + create_entity_matrix(transform, (entity_t*)entity, is_viewer_weapon); + + instance_model_lights(model->num_light_polys, model->light_polys, transform); + } } } diff --git a/src/refresh/vkpt/models.c b/src/refresh/vkpt/models.c index 455315938..ecc8a6980 100644 --- a/src/refresh/vkpt/models.c +++ b/src/refresh/vkpt/models.c @@ -100,6 +100,111 @@ static void export_obj_frames(model_t* model, const char* path_pattern) } } +static void extract_model_lights(model_t* model) +{ + // Count the triangles in the model that have a material with the is_light flag set + + int num_lights = 0; + + for (int mesh_idx = 0; mesh_idx < model->nummeshes; mesh_idx++) + { + const maliasmesh_t* mesh = model->meshes + mesh_idx; + for (int skin_idx = 0; skin_idx < mesh->numskins; skin_idx++) + { + const pbr_material_t* mat = mesh->materials[skin_idx]; + if ((mat->flags & MATERIAL_FLAG_LIGHT) != 0 && mat->image_emissive) + { + if (mesh->numskins != 1) + { + Com_DPrintf("Warning: model %s mesh %d has LIGHT material(s) but more than 1 skin (%d), " + "which is unsupported.\n", model->name, mesh->numskins); + return; + } + + num_lights += mesh->numtris; + } + } + } + + // If there are no light triangles, there's nothing to do + if (num_lights == 0) + return; + + // Validate our current implementation limitations, give warnings if they are hit + + if (model->numframes > 1) + { + Com_DPrintf("Warning: model %s has LIGHT material(s) but more than 1 vertex animation frame, " + "which is unsupported.\n", model->name); + return; + } + + if (model->iqmData && model->iqmData->blend_weights) + { + Com_DPrintf("Warning: model %s has LIGHT material(s) and skeletal animations, " + "which is unsupported.\n", model->name); + return; + } + + // Actually extract the lights now + + model->light_polys = Hunk_Alloc(&model->hunk, sizeof(light_poly_t) * num_lights); + model->num_light_polys = num_lights; + + num_lights = 0; + + for (int mesh_idx = 0; mesh_idx < model->nummeshes; mesh_idx++) + { + const maliasmesh_t* mesh = model->meshes + mesh_idx; + assert(mesh->numskins == 1); + assert(mesh->indices); + assert(mesh->positions); + + pbr_material_t* mat = mesh->materials[0]; + if ((mat->flags & MATERIAL_FLAG_LIGHT) != 0 && mat->image_emissive) + { + for (int tri_idx = 0; tri_idx < mesh->numtris; tri_idx++) + { + light_poly_t* light = model->light_polys + num_lights; + num_lights++; + + int i0 = mesh->indices[tri_idx * 3 + 0]; + int i1 = mesh->indices[tri_idx * 3 + 1]; + int i2 = mesh->indices[tri_idx * 3 + 2]; + + assert(i0 < mesh->numverts); + assert(i1 < mesh->numverts); + assert(i2 < mesh->numverts); + + memcpy(light->positions + 0, mesh->positions + i0, sizeof(vec3_t)); + memcpy(light->positions + 3, mesh->positions + i1, sizeof(vec3_t)); + memcpy(light->positions + 6, mesh->positions + i2, sizeof(vec3_t)); + + // Cluster is assigned after model instancing and transformation + light->cluster = -1; + + light->material = mat; + + VectorCopy(mat->image_emissive->light_color, light->color); + + if (!mat->image_emissive->entire_texture_emissive) + { + // This extraction doesn't support partially emissive textures, so pretend the entire + // texture is uniformly emissive and dim the light according to the area fraction. + light->emissive_factor = + (mat->image_emissive->max_light_texcoord[0] - mat->image_emissive->min_light_texcoord[0]) * + (mat->image_emissive->max_light_texcoord[1] - mat->image_emissive->min_light_texcoord[1]); + } + else + light->emissive_factor = 1.f; + + get_triangle_off_center(light->positions, light->off_center, NULL, 1.f); + + } + } + } +} + qerror_t MOD_LoadMD2_RTX(model_t *model, const void *rawdata, size_t length, const char* mod_name) { dmd2header_t header; @@ -357,6 +462,8 @@ qerror_t MOD_LoadMD2_RTX(model_t *model, const void *rawdata, size_t length, con dst_mesh->indices[i + 2] = tmp; } + extract_model_lights(model); + Hunk_End(&model->hunk); return Q_ERR_SUCCESS; @@ -565,6 +672,8 @@ qerror_t MOD_LoadMD3_RTX(model_t *model, const void *rawdata, size_t length, con //if (strstr(model->name, "v_blast")) // export_obj_frames(model, "export/v_blast_%d.obj"); + extract_model_lights(model); + Hunk_End(&model->hunk); return Q_ERR_SUCCESS; @@ -633,6 +742,8 @@ qerror_t MOD_LoadIQM_RTX(model_t* model, const void* rawdata, size_t length, con mesh->numskins = 1; // looks like IQM only supports one skin? } + extract_model_lights(model); + Hunk_End(&model->hunk); return Q_ERR_SUCCESS; diff --git a/src/refresh/vkpt/vertex_buffer.c b/src/refresh/vkpt/vertex_buffer.c index 7823c12b9..c71d80d0b 100644 --- a/src/refresh/vkpt/vertex_buffer.c +++ b/src/refresh/vkpt/vertex_buffer.c @@ -199,6 +199,27 @@ inject_model_lights(bsp_mesh_t* bsp_mesh, bsp_t* bsp, int num_model_lights, ligh 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]; + } + + // See if we have enough room in the interaction buffer + + if (required_size > MAX_LIGHT_LIST_NODES) + { + 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)); + + return; + } + // Copy the static light lists, and make room in these lists to inject the model lights int tail = 0; @@ -209,6 +230,9 @@ inject_model_lights(bsp_mesh_t* bsp_mesh, bsp_t* bsp, int num_model_lights, ligh dst_list_offsets[c] = tail; 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); + if (max_cluster_model_lights[c] > 0) { memset(dst_lists + tail, 0xff, sizeof(uint32_t) * max_cluster_model_lights[c]); } diff --git a/src/refresh/vkpt/vkpt.h b/src/refresh/vkpt/vkpt.h index 8934afcf1..6fdbce235 100644 --- a/src/refresh/vkpt/vkpt.h +++ b/src/refresh/vkpt/vkpt.h @@ -321,16 +321,6 @@ LIST_EXTENSIONS_INSTANCE #define MAX_SKY_CLUSTERS 1024 -typedef struct light_poly_s { - float positions[9]; // 3x vec3_t - vec3_t off_center; - vec3_t color; - struct pbr_material_s* material; - int cluster; - int style; - float emissive_factor; -} light_poly_t; - typedef struct bsp_model_s { uint32_t idx_offset; uint32_t idx_count;