0
0
Fork 0
mirror of https://git.code.sf.net/p/quake/quakeforge synced 2025-04-14 13:21:43 +00:00

[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.
This commit is contained in:
Bill Currie 2023-12-15 23:56:41 +09:00
parent 1c13879fb9
commit f282bfc045
5 changed files with 307 additions and 4 deletions
include/QF/Vulkan
libs/video/renderer/vulkan

View file

@ -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

View file

@ -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;

View file

@ -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; },

View file

@ -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);

View file

@ -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);