From f282bfc045a45dcc41a8c7d6cd9687bef97ab96a Mon Sep 17 00:00:00 2001 From: Bill Currie Date: Fri, 15 Dec 2023 23:56:41 +0900 Subject: [PATCH] [vulkan] Use occlusion queries for culling lights The info isn't used yet, but this shows that vulkan's occlusion queries are at least somewhat useful. However, the technique isn't perfect: infinite radius lights (1/r and 1/r^2) are difficult to cull, and all lights can poke through thin enough walls, and then lights containing the camera get culled incorrectly (will need a separate test). Still, it looks like it will help once everything is tied together. --- include/QF/Vulkan/funclist.h | 8 + include/QF/Vulkan/qf_lighting.h | 3 + libs/video/renderer/vulkan/rp_main_def.plist | 167 +++++++++++++++++- .../renderer/vulkan/shader/light_splat.vert | 5 +- libs/video/renderer/vulkan/vulkan_lighting.c | 128 +++++++++++++- 5 files changed, 307 insertions(+), 4 deletions(-) diff --git a/include/QF/Vulkan/funclist.h b/include/QF/Vulkan/funclist.h index 8a68ae90c..33542926b 100644 --- a/include/QF/Vulkan/funclist.h +++ b/include/QF/Vulkan/funclist.h @@ -196,6 +196,14 @@ DEVICE_LEVEL_VULKAN_FUNCTION (vkCmdDrawIndexed) DEVICE_LEVEL_VULKAN_FUNCTION (vkCmdDrawIndexedIndirect) DEVICE_LEVEL_VULKAN_FUNCTION (vkCmdDrawIndirect) +DEVICE_LEVEL_VULKAN_FUNCTION (vkCreateQueryPool) +DEVICE_LEVEL_VULKAN_FUNCTION (vkDestroyQueryPool) +DEVICE_LEVEL_VULKAN_FUNCTION (vkCmdResetQueryPool) +DEVICE_LEVEL_VULKAN_FUNCTION (vkCmdBeginQuery) +DEVICE_LEVEL_VULKAN_FUNCTION (vkCmdEndQuery) +DEVICE_LEVEL_VULKAN_FUNCTION (vkCmdCopyQueryPoolResults) +DEVICE_LEVEL_VULKAN_FUNCTION (vkGetQueryPoolResults) + #undef DEVICE_LEVEL_VULKAN_FUNCTION #ifndef DEVICE_LEVEL_VULKAN_FUNCTION_FROM_EXTENSION diff --git a/include/QF/Vulkan/qf_lighting.h b/include/QF/Vulkan/qf_lighting.h index 6e987f4a4..d81ee2c3c 100644 --- a/include/QF/Vulkan/qf_lighting.h +++ b/include/QF/Vulkan/qf_lighting.h @@ -85,6 +85,9 @@ typedef struct lightingframe_s { VkDescriptorSet lights_set; VkDescriptorSet attach_set; + VkQueryPool query; + VkFence fence; + VkBuffer shadowmat_buffer; VkBuffer shadowmat_id_buffer; VkBuffer light_buffer; diff --git a/libs/video/renderer/vulkan/rp_main_def.plist b/libs/video/renderer/vulkan/rp_main_def.plist index bbb7677ae..587f8e5f7 100644 --- a/libs/video/renderer/vulkan/rp_main_def.plist +++ b/libs/video/renderer/vulkan/rp_main_def.plist @@ -1100,6 +1100,11 @@ descriptorSetLayouts = { }; }; images = { + occlusion_depth = { + @inherit = $image_base; + format = x8_d24_unorm_pack32; + usage = depth_stencil_attachment|input_attachment|transient_attachment; + }; depth = { @inherit = $image_base; format = x8_d24_unorm_pack32; @@ -1136,6 +1141,11 @@ images = { format = r32_uint; }; + occlusion_cube_depth = { + @inherit = $cube_image_base; + format = x8_d24_unorm_pack32; + usage = depth_stencil_attachment|input_attachment|transient_attachment; + }; cube_depth = { @inherit = $cube_image_base; format = x8_d24_unorm_pack32; @@ -1174,6 +1184,14 @@ images = { }; }; imageviews = { + occlusion_depth = { + @inherit = $view_base; + image = occlusion_depth; + format = $images.occlusion_depth.format; + subresourceRange = { + aspectMask = depth; + }; + }; depth = { @inherit = $view_base; image = depth; @@ -1216,6 +1234,14 @@ imageviews = { @inherit = $view_base; image = entid; }; + occlusion_cube_depth = { + @inherit = $cube_view_base; + image = occlusion_cube_depth; + format = $images.occlusion_cube_depth.format; + subresourceRange = { + aspectMask = depth; + }; + }; cube_depth = { @inherit = $cube_view_base; image = cube_depth; @@ -1266,6 +1292,127 @@ output = { finalLayout = shader_read_only_optimal; }; renderpasses = { + occlusion = { + color = "[0.8, 0.6, 0, 1]"; + framebuffer = { + width = $render_output.extent.width; + height = $render_output.extent.height; + layers = 1; + attachments = { + depth = { + @inherit = $attachment_base; + format = $images.depth.format; + loadOp = clear; + finalLayout = depth_stencil_attachment_optimal; + clearValue = { depthStencil = { depth = 0; stencil = 0; }; }; + view = occlusion_depth; + }; + }; + }; + subpasses = { + depth = { + color = "[ 0.5, 0.5, 0.5, 1]"; + attachments = { + depth = { + depth = depth_stencil_attachment_optimal; + }; + }; + + base_pipeline = { + @inherit = $pipeline_base; + depthStencil = $depth_test_and_write; + rasterization = $cw_cull_back; + }; + pipelines = { + bsp:depth = { + color = $color.bsp; + tasks = ( + { func = bsp_draw_queue; + params = (main, solid, 0); }, + { func = bsp_draw_queue; + params = (main, sky, 0); }, + ); + + stages = ( + $brush.shader.depth_vertex, + ); + vertexInput = { + bindings = ( + "$brush.vertexInput.bindings[0]", + "$brush.vertexInput.bindings[1]", + ); + attributes = ( + "$brush.vertexInput.attributes[0]", + "$brush.vertexInput.attributes[2]", + ); + }; + inputAssembly = $brush.inputAssembly; + layout = $brush.layout; + }; + }; + }; + lightcull = { + color = "[ 0.7, 0.5, 0, 1]"; + dependencies = { + depth = $depth_dependency; + }; + attachments = { + depth = { + depth = depth_stencil_attachment_optimal; + }; + }; + + base_pipeline = { + @inherit = $pipeline_base; + depthStencil = $depth_test_and_write; + rasterization = $cw_cull_back; + }; + pipelines = { + cull_lights = { + @inherit = $compose_base; + + color = $color.lights; + tasks = ( + { func = lighting_bind_descriptors; + params = (debug, none); }, + { func = lighting_draw_hulls; }, + ); + + stages = ( + $lighting.shader.vertex_splat, + $lighting.shader.debug_fragment, + ); + vertexInput = $lighting.vertexInput_splat; + inputAssembly = $lighting.inputAssembly; + layout = $lighting.splat_layout; + rasterization = $cw_cull_back; + depthStencil = $depth_test_only; + }; + }; + }; + }; + }; + occlusion_cube = { + @inherit = $renderpasses.occlusion; + @next = (VkRenderPassMultiviewCreateInfo, { + viewMasks = (0x3fu, 0x3fu); + }); + framebuffer = { + width = "min($render_output.extent.width,$render_output.extent.height)"; + height = "min($render_output.extent.width,$render_output.extent.height)"; + layers = 1; + attachments = { + depth = { + @inherit = $attachment_base; + format = $images.cube_depth.format; + loadOp = clear; + finalLayout = depth_stencil_attachment_optimal; + clearValue = { depthStencil = { depth = 0; stencil = 0; }; }; + view = occlusion_cube_depth; + }; + }; + }; + }; deferred = { color = "[0, 1, 0, 1]"; framebuffer = { @@ -2377,12 +2524,30 @@ steps = { params = (main); }, { func = scene_draw_viewmodel; }, { func = lighting_update_lights; }, + { func = update_framebuffer; + params = ("\"light_cull\""); }, ); }; }; + light_cull = { + color = "[0.8, 0.6, 0, 1]"; + dependencies = (world); + process = { + tasks = ( + { func = lighting_cull_lights; + params = ("\"light_cull\""); }, + ); + }; + render = { + renderpasses = { + occlusion = $renderpasses.occlusion; + occlusion_cube = $renderpasses.occlusion_cube; + }; + }; + }; shadow = { color = "[0.3, 0.3, 0.3]"; - dependencies = (world); + dependencies = (light_cull); process = { tasks = ( { func = lighting_setup_shadow; }, diff --git a/libs/video/renderer/vulkan/shader/light_splat.vert b/libs/video/renderer/vulkan/shader/light_splat.vert index c131e9e33..ce1155041 100644 --- a/libs/video/renderer/vulkan/shader/light_splat.vert +++ b/libs/video/renderer/vulkan/shader/light_splat.vert @@ -33,7 +33,10 @@ void main (void) { LightData l = lights[light_index]; - float sz = l.attenuation.w > 0 ? 1 / l.attenuation.w : sqrt(abs(l.color.w)); + float sz = l.attenuation.w > 0 ? 1 / l.attenuation.w + : l.attenuation.x > 0 ? sqrt(abs(l.color.w/l.attenuation.x)) + : l.attenuation.y > 0 ? abs(l.color.w/l.attenuation.y) + : sqrt(abs(l.color.w)); float c = l.direction.w; float sxy = sz * (c < 0 ? (sqrt (1 - c*c) / -c) : 1); vec3 scale = vec3 (sxy, sxy, sz); diff --git a/libs/video/renderer/vulkan/vulkan_lighting.c b/libs/video/renderer/vulkan/vulkan_lighting.c index 40a122091..98ff9cb71 100644 --- a/libs/video/renderer/vulkan/vulkan_lighting.c +++ b/libs/video/renderer/vulkan/vulkan_lighting.c @@ -984,6 +984,106 @@ lighting_draw_splats (const exprval_t **params, exprval_t *result, } } +static void +lighting_cull_lights (const exprval_t **params, exprval_t *result, + exprctx_t *ectx) +{ + auto taskctx = (qfv_taskctx_t *) ectx; + auto ctx = taskctx->ctx; + auto device = ctx->device; + auto dfunc = device->funcs; + auto lctx = ctx->lighting_context; + + auto lframe = &lctx->frames.a[ctx->curFrame]; + auto queue = lframe->light_queue; + uint32_t count = queue[ST_CUBE].count + queue[ST_PLANE].count; + if (!count) { + return; + } + + auto light_cull = QFV_GetStep (params[0], ctx->render_context->job); + auto render = light_cull->render; + + auto cmd = QFV_GetCmdBuffer (ctx, false); + dfunc->vkBeginCommandBuffer (cmd, &(VkCommandBufferBeginInfo) { + .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, + .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT, + }); + { + qftVkScopedZoneC (taskctx->frame->qftVkCtx, cmd, "reset", 0xc0a000); + dfunc->vkCmdResetQueryPool (cmd, lframe->query, 0, MaxLights); + } + auto renderpass = &render->renderpasses[0]; + QFV_RunRenderPassCmd (cmd, ctx, renderpass, 0); + dfunc->vkEndCommandBuffer (cmd); + + qfMessageL ("submit"); + auto dev_queue = &device->queue; + VkSubmitInfo submitInfo = { + .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, + .commandBufferCount = 1, + .pCommandBuffers = &cmd, + }; + dfunc->vkResetFences (device->dev, 1, &lframe->fence); + dfunc->vkQueueSubmit (dev_queue->queue, 1, &submitInfo, lframe->fence); + dfunc->vkWaitForFences (device->dev, 1, &lframe->fence, VK_TRUE, 200000000); + + uint32_t frag_counts[count]; + VkDeviceSize size = sizeof (frag_counts); + dfunc->vkGetQueryPoolResults (device->dev, lframe->query, 0, count, + size, frag_counts, sizeof (uint32_t), + VK_QUERY_RESULT_WAIT_BIT); + uint32_t c = 0; + for (uint32_t i = 0; i < count; i++) { + c += frag_counts[i] != 0; + } + if (1) printf ("%d/%d visible\n", c, count); +} + +static void +draw_hull (uint32_t indexCount, uint32_t firstIndex, int32_t vertOffset, + uint32_t hull, uint32_t id, VkCommandBuffer cmd, VkQueryPool query, + qfv_devfuncs_t *dfunc, qftVkCtx_t *vk) +{ + qfZoneNamed (zone, true); + qftVkScopedZoneC (vk, cmd, "draw_hull", 0xc0a000); + dfunc->vkCmdBeginQuery (cmd, query, id, 0); + dfunc->vkCmdDrawIndexed (cmd, indexCount, 1, firstIndex, vertOffset, hull); + dfunc->vkCmdEndQuery (cmd, query, id); +} + +static void +lighting_draw_hulls (const exprval_t **params, exprval_t *result, + exprctx_t *ectx) +{ + qfZoneNamed (zone, true); + auto taskctx = (qfv_taskctx_t *) ectx; + auto ctx = taskctx->ctx; + auto device = ctx->device; + auto dfunc = device->funcs; + auto lctx = ctx->lighting_context; + auto cmd = taskctx->cmd; + + auto lframe = &lctx->frames.a[ctx->curFrame]; + uint32_t id = 0; + if (lframe->light_queue[ST_CUBE].count) { + auto q = lframe->light_queue[ST_CUBE]; + for (uint32_t i = 0; i < q.count; i++) { + uint32_t hull = q.start + i; + draw_hull (num_ico_inds, 0, 0, hull, id++, + cmd, lframe->query, dfunc, taskctx->frame->qftVkCtx); + } + } + if (lframe->light_queue[ST_PLANE].count) { + auto q = lframe->light_queue[ST_PLANE]; + for (uint32_t i = 0; i < q.count; i++) { + uint32_t hull = q.start + i; + draw_hull (num_cone_inds, num_ico_inds, 12, hull, id++, + cmd, lframe->query, dfunc, taskctx->frame->qftVkCtx); + } + } +} + static void lighting_draw_lights (const exprval_t **params, exprval_t *result, exprctx_t *ectx) @@ -1090,6 +1190,15 @@ static exprfunc_t lighting_draw_splats_func[] = { { .func = lighting_draw_splats }, {} }; +static exprfunc_t lighting_cull_lights_func[] = { + { .func = lighting_cull_lights, .num_params = 1, + .param_types = stepref_param }, + {} +}; +static exprfunc_t lighting_draw_hulls_func[] = { + { .func = lighting_draw_hulls }, + {} +}; static exprfunc_t lighting_draw_lights_func[] = { { .func = lighting_draw_lights, .num_params = 2, .param_types = shadow_type_param }, @@ -1111,6 +1220,8 @@ static exprsym_t lighting_task_syms[] = { { "lighting_bind_descriptors", &cexpr_function, lighting_bind_descriptors_func }, { "lighting_draw_splats", &cexpr_function, lighting_draw_splats_func }, + { "lighting_cull_lights", &cexpr_function, lighting_cull_lights_func }, + { "lighting_draw_hulls", &cexpr_function, lighting_draw_hulls_func }, { "lighting_draw_lights", &cexpr_function, lighting_draw_lights_func }, { "lighting_setup_shadow", &cexpr_function, lighting_setup_shadow_func }, { "lighting_draw_shadow_maps", &cexpr_function, @@ -1554,6 +1665,13 @@ Vulkan_Lighting_Setup (vulkan_ctx_t *ctx) .pBufferInfo = &bufferInfo[6], }, }; dfunc->vkUpdateDescriptorSets (device->dev, 7, bufferWrite, 0, 0); + + dfunc->vkCreateQueryPool (device->dev, &(VkQueryPoolCreateInfo) { + .sType = VK_STRUCTURE_TYPE_QUERY_POOL_CREATE_INFO, + .queryType = VK_QUERY_TYPE_OCCLUSION, + .queryCount = MaxLights, + }, 0, &lframe->query); + lframe->fence = QFV_CreateFence (device, 1); } size_t target_count = MaxLights * 6; size_t target_size = frames * sizeof (uint16_t[target_count]); @@ -1618,14 +1736,20 @@ clear_shadows (vulkan_ctx_t *ctx) void Vulkan_Lighting_Shutdown (vulkan_ctx_t *ctx) { - qfv_device_t *device = ctx->device; - lightingctx_t *lctx = ctx->lighting_context; + auto device = ctx->device; + auto dfunc = device->funcs; + auto lctx = ctx->lighting_context; clear_shadows (ctx); QFV_DestroyResource (device, lctx->light_resources); free (lctx->light_resources); + for (size_t i = 0; i < lctx->frames.size; i++) { + auto lframe = &lctx->frames.a[i]; + dfunc->vkDestroyQueryPool (device->dev, lframe->query, 0); + dfunc->vkDestroyFence (device->dev, lframe->fence, 0); + } free (lctx->frames.a[0].stage_targets); DARRAY_CLEAR (&lctx->light_mats); DARRAY_CLEAR (&lctx->light_control);