[vulkan] Hook up a shadow render job step

It doesn't do much yet, but did help in getting light ids working.
This commit is contained in:
Bill Currie 2023-07-24 10:23:05 +09:00
parent 05f0fe9204
commit f4d6a41901
3 changed files with 221 additions and 197 deletions

View file

@ -37,6 +37,7 @@
#include "QF/Vulkan/qf_vid.h" #include "QF/Vulkan/qf_vid.h"
#include "QF/Vulkan/command.h" #include "QF/Vulkan/command.h"
#include "QF/Vulkan/image.h" #include "QF/Vulkan/image.h"
#include "QF/Vulkan/render.h"
#include "QF/simd/types.h" #include "QF/simd/types.h"
typedef struct qfv_lightmatset_s DARRAY_TYPE (mat4f_t) qfv_lightmatset_t; typedef struct qfv_lightmatset_s DARRAY_TYPE (mat4f_t) qfv_lightmatset_t;
@ -84,19 +85,18 @@ typedef struct lightingframeset_s
DARRAY_TYPE (lightingframe_t) lightingframeset_t; DARRAY_TYPE (lightingframe_t) lightingframeset_t;
typedef struct light_renderer_s { typedef struct light_renderer_s {
VkRenderPass renderPass; // shared uint8_t renderpass_index;
VkFramebuffer framebuffer; uint8_t image_index;
VkImage image; // shared uint16_t size;
VkImageView view; uint16_t layer;
uint32_t size; uint8_t numLayers;
uint32_t layer; uint8_t mode;
uint32_t numLayers;
int mode;
} light_renderer_t; } light_renderer_t;
typedef struct light_renderer_set_s typedef struct light_renderer_set_s
DARRAY_TYPE (light_renderer_t) light_renderer_set_t; DARRAY_TYPE (light_renderer_t) light_renderer_set_t;
typedef struct lightingctx_s { typedef struct lightingctx_s {
lightingframeset_t frames; lightingframeset_t frames;
VkSampler sampler; VkSampler sampler;
@ -108,9 +108,7 @@ typedef struct lightingctx_s {
light_renderer_set_t light_renderers; light_renderer_set_t light_renderers;
VkRenderPass renderpass_6; qfv_attachmentinfo_t shadow_info;
VkRenderPass renderpass_4;
VkRenderPass renderpass_1;
VkBuffer splat_verts; VkBuffer splat_verts;
VkBuffer splat_inds; VkBuffer splat_inds;

View file

@ -1017,6 +1017,7 @@ images = {
usage = color_attachment|input_attachment|sampled; usage = color_attachment|input_attachment|sampled;
format = $render_output.format; format = $render_output.format;
}; };
cube_depth = { cube_depth = {
@inherit = $cube_image_base; @inherit = $cube_image_base;
format = x8_d24_unorm_pack32; format = x8_d24_unorm_pack32;
@ -1769,6 +1770,113 @@ renderpasses = {
}; };
}; };
}; };
shadow = {
color = "[0.2, 0.2, 0.2, 1]";
framebuffer = {
layers = 1;
attachments = {
shadow = $shadow;
};
};
subpasses = {
shadow = {
color = "[ 0.5, 0.5, 0.5, 1]";
attachments = {
depth = {
shadow = depth_stencil_attachment_optimal;
};
};
base_pipeline = {
@inherit = $pipeline_base;
depthStencil = $depth_test_and_write;
rasterization = $cw_cull_back;
};
pipelines = {
bsp:shadow = {
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;
};
alias:shadow = {
color = $color.alias;
tasks = (
{ func = alias_draw;
params = (0); },
);
stages = (
$alias.shader.depth_vertex,
);
vertexInput = {
// depth/shadow pass doesn't use UVs
bindings = (
"$alias.vertexInput.bindings[0]",
"$alias.vertexInput.bindings[1]",
);
attributes = (
"$alias.vertexInput.attributes[0]",
"$alias.vertexInput.attributes[1]",
"$alias.vertexInput.attributes[2]",
"$alias.vertexInput.attributes[3]",
);
};
inputAssembly = $alias.inputAssembly;
layout = $alias.layout;
};
iqm:shadow = {
color = $color.iqm;
tasks = (
{ func = iqm_draw;
params = (0); },
);
stages = (
$iqm.shader.depth_vertex,
);
vertexInput = $iqm.vertexInput;
inputAssembly = $iqm.inputAssembly;
layout = $iqm.layout;
};
};
};
};
output = shadow;
};
cascade_shadow = {
@inherit = $renderpasses.shadow;
@next = (VkRenderPassMultiviewCreateInfo, {
viewMasks = (0x0fu);
});
output = cascade_shadow;
};
cube_shadow = {
@inherit = $renderpasses.shadow;
@next = (VkRenderPassMultiviewCreateInfo, {
viewMasks = (0x0fu);
});
output = cube_shadow;
};
}; };
steps = { steps = {
wait_on_fence = { wait_on_fence = {
@ -1810,10 +1918,6 @@ steps = {
}; };
}; };
}; };
shadow = {
dependencies = (wait_on_fence);
//currently empty
};
world = { world = {
dependencies = (wait_on_fence); dependencies = (wait_on_fence);
process = { process = {
@ -1825,6 +1929,21 @@ steps = {
); );
}; };
}; };
shadow = {
dependencies = (world);
process = {
tasks = (
{ func = lighting_draw_shadow_maps; },
);
};
render = {
renderpasses = {
shadow = $renderpasses.shadow;
cascade_shadow = $renderpasses.cascade_shadow;
cube_shadow = $renderpasses.cube_shadow;
};
};
};
setup_main = { setup_main = {
dependencies = (wait_on_fence); dependencies = (wait_on_fence);
process = { process = {

View file

@ -93,79 +93,37 @@ static int cone_inds[] = {
1, 6, 5, 4, 3, 2, 1, 6, 5, 4, 3, 2,
}; };
#define num_cone_inds (sizeof (cone_inds) / sizeof (cone_inds[0])) #define num_cone_inds (sizeof (cone_inds) / sizeof (cone_inds[0]))
#if 0
static const light_t *
get_light (entity_t ent)
{
return Ent_GetComponent (ent.id, scene_light, ent.reg);
}
static int
get_lightstyle (entity_t ent)
{
return *(int *) Ent_GetComponent (ent.id, scene_lightstyle, ent.reg);
}
static uint32_t
get_lightid (entity_t ent)
{
return *(uint32_t *) Ent_GetComponent (ent.id, scene_lightid, ent.reg);
}
static void static void
lighting_draw_maps (qfv_orenderframe_t *rFrame) set_lightid (uint32_t ent, ecs_registry_t *reg, uint32_t id)
{ {
vulkan_ctx_t *ctx = rFrame->vulkan_ctx; Ent_SetComponent (ent, scene_lightid, reg, &id);
qfv_device_t *device = ctx->device;
qfv_devfuncs_t *dfunc = device->funcs;
lightingctx_t *lctx = ctx->lighting_context;
if (rFrame->subpassCmdSets[0].size) {
__auto_type sets = &rFrame->subpassCmdSets[0];
dfunc->vkFreeCommandBuffers (device->dev, lctx->cmdpool,
sets->size, sets->a);
sets->size = 0;
} }
if (!lctx->ldata || !lctx->ldata->lights.size) { static void
return; lighting_draw_shadow_maps (const exprval_t **params, exprval_t *result,
} exprctx_t *ectx)
if (!lctx->light_renderers.a[0].renderPass) {
//FIXME goes away when lighting implemented properly
return;
}
__auto_type bufferset = QFV_AllocCommandBufferSet (1, alloca);
QFV_AllocateCommandBuffers (device, lctx->cmdpool, 0, bufferset);
VkCommandBuffer cmd = bufferset->a[0];
QFV_duSetObjectName (device, VK_OBJECT_TYPE_COMMAND_BUFFER,
cmd, va (ctx->va_ctx, "lighting:%d", ctx->curFrame));
VkCommandBufferBeginInfo beginInfo = {
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
};
dfunc->vkBeginCommandBuffer (cmd, &beginInfo);
__auto_type rp = rFrame->renderpass;
QFV_CmdBeginLabel (device, cmd, rp->name, rp->color);
__auto_type lr = &lctx->light_renderers.a[0];
VkRenderPassBeginInfo renderPassInfo = {
.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
.renderArea = { {0, 0}, {lr->size, lr->size} },
.framebuffer = lr->framebuffer,
.renderPass = lr->renderPass,
.pClearValues = lctx->qfv_renderpass->clearValues->a,
};
__auto_type subpassContents = VK_SUBPASS_CONTENTS_SECONDARY_COMMAND_BUFFERS;
if (renderPassInfo.renderPass) {
dfunc->vkCmdBeginRenderPass (cmd, &renderPassInfo, subpassContents);
//...
dfunc->vkCmdEndRenderPass (cmd);
}
QFV_CmdEndLabel (device, cmd);
dfunc->vkEndCommandBuffer (cmd);
DARRAY_APPEND (&rFrame->subpassCmdSets[0], cmd);
}
void
Vulkan_Lighting_CreateRenderPasses (vulkan_ctx_t *ctx)
{ {
// extents are dynamic and filled in for each light
// frame buffers are highly dynamic
__auto_type rp = QFV_RenderPass_New (ctx, "shadow", lighting_draw_maps);
QFV_RenderPass_CreateRenderPass (rp);
rp->primary_commands = 1;
rp->order = QFV_rp_shadowmap;
DARRAY_APPEND (&ctx->renderPasses, rp);
lctx->qfv_renderpass = rp;
} }
#endif
static void static void
lighting_update_lights (const exprval_t **params, exprval_t *result, lighting_update_lights (const exprval_t **params, exprval_t *result,
exprctx_t *ectx) exprctx_t *ectx)
@ -219,9 +177,11 @@ lighting_update_lights (const exprval_t **params, exprval_t *result,
auto queue = r_ent_queue; //FIXME fetch from scene auto queue = r_ent_queue; //FIXME fetch from scene
for (size_t i = 0; i < queue->ent_queues[mod_light].size; i++) { for (size_t i = 0; i < queue->ent_queues[mod_light].size; i++) {
entity_t ent = queue->ent_queues[mod_light].a[i]; entity_t ent = queue->ent_queues[mod_light].a[i];
light_t *l = Ent_GetComponent (ent.id, scene_light, ent.reg); auto l = get_light (ent);
int ls = *(int *) Ent_GetComponent (ent.id, scene_lightstyle, auto ls = get_lightstyle (ent);
ent.reg); if (!d_lightstylevalue[ls]) {
continue;
}
uint32_t id = light_data->lightCount++; uint32_t id = light_data->lightCount++;
auto light = &light_data->lights[id]; auto light = &light_data->lights[id];
@ -430,6 +390,10 @@ static exprfunc_t lighting_draw_lights_func[] = {
{ .func = lighting_draw_lights }, { .func = lighting_draw_lights },
{} {}
}; };
static exprfunc_t lighting_draw_shadow_maps_func[] = {
{ .func = lighting_draw_shadow_maps },
{}
};
static exprsym_t lighting_task_syms[] = { static exprsym_t lighting_task_syms[] = {
{ "lighting_update_lights", &cexpr_function, lighting_update_lights_func }, { "lighting_update_lights", &cexpr_function, lighting_update_lights_func },
{ "lighting_update_descriptors", &cexpr_function, { "lighting_update_descriptors", &cexpr_function,
@ -439,6 +403,8 @@ static exprsym_t lighting_task_syms[] = {
{ "lighting_draw_splats", &cexpr_function, lighting_draw_splats_func }, { "lighting_draw_splats", &cexpr_function, lighting_draw_splats_func },
{ "lighting_draw_flats", &cexpr_function, lighting_draw_flats_func }, { "lighting_draw_flats", &cexpr_function, lighting_draw_flats_func },
{ "lighting_draw_lights", &cexpr_function, lighting_draw_lights_func }, { "lighting_draw_lights", &cexpr_function, lighting_draw_lights_func },
{ "lighting_draw_shadow_maps", &cexpr_function,
lighting_draw_shadow_maps_func },
{} {}
}; };
@ -449,6 +415,22 @@ Vulkan_Lighting_Init (vulkan_ctx_t *ctx)
ctx->lighting_context = lctx; ctx->lighting_context = lctx;
QFV_Render_AddTasks (ctx, lighting_task_syms); QFV_Render_AddTasks (ctx, lighting_task_syms);
lctx->shadow_info = (qfv_attachmentinfo_t) {
.name = "$shadow",
.format = VK_FORMAT_X8_D24_UNORM_PACK32,
.samples = 1,
.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR,
.storeOp = VK_ATTACHMENT_STORE_OP_STORE,
.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE,
.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE,
.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
.finalLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
};
qfv_attachmentinfo_t *attachments[] = {
&lctx->shadow_info,
};
QFV_Render_AddAttachments (ctx, 1, attachments);
} }
static void static void
@ -660,14 +642,8 @@ static void
clear_shadows (vulkan_ctx_t *ctx) clear_shadows (vulkan_ctx_t *ctx)
{ {
qfv_device_t *device = ctx->device; qfv_device_t *device = ctx->device;
qfv_devfuncs_t *dfunc = device->funcs;
lightingctx_t *lctx = ctx->lighting_context; lightingctx_t *lctx = ctx->lighting_context;
for (size_t i = 0; i < lctx->light_renderers.size; i++) {
__auto_type lr = &lctx->light_renderers.a[i];
dfunc->vkDestroyFramebuffer (device->dev, lr->framebuffer, 0);
dfunc->vkDestroyImageView (device->dev, lr->view, 0);
}
if (lctx->shadow_resources) { if (lctx->shadow_resources) {
QFV_DestroyResource (device, lctx->shadow_resources); QFV_DestroyResource (device, lctx->shadow_resources);
free (lctx->shadow_resources); free (lctx->shadow_resources);
@ -681,15 +657,10 @@ void
Vulkan_Lighting_Shutdown (vulkan_ctx_t *ctx) Vulkan_Lighting_Shutdown (vulkan_ctx_t *ctx)
{ {
qfv_device_t *device = ctx->device; qfv_device_t *device = ctx->device;
qfv_devfuncs_t *dfunc = device->funcs;
lightingctx_t *lctx = ctx->lighting_context; lightingctx_t *lctx = ctx->lighting_context;
clear_shadows (ctx); clear_shadows (ctx);
dfunc->vkDestroyRenderPass (device->dev, lctx->renderpass_6, 0);
dfunc->vkDestroyRenderPass (device->dev, lctx->renderpass_4, 0);
dfunc->vkDestroyRenderPass (device->dev, lctx->renderpass_1, 0);
QFV_DestroyResource (device, lctx->light_resources); QFV_DestroyResource (device, lctx->light_resources);
free (lctx->light_resources); free (lctx->light_resources);
@ -702,6 +673,16 @@ Vulkan_Lighting_Shutdown (vulkan_ctx_t *ctx)
static vec4f_t ref_direction = { 0, 0, 1, 0 }; static vec4f_t ref_direction = { 0, 0, 1, 0 };
// Quake's world is z-up, x-forward, y-left, but Vulkan's world is
// z-forward, x-right, y-down.
//FIXME copy of z_up in vulkan_matrices.c
static mat4f_t z_up = {
{ 0, 0, 1, 0},
{-1, 0, 0, 0},
{ 0,-1, 0, 0},
{ 0, 0, 0, 1},
};
static void static void
create_light_matrices (lightingctx_t *lctx) create_light_matrices (lightingctx_t *lctx)
{ {
@ -711,19 +692,12 @@ create_light_matrices (lightingctx_t *lctx)
DARRAY_RESIZE (&lctx->light_mats, light_pool->count); DARRAY_RESIZE (&lctx->light_mats, light_pool->count);
for (size_t i = 0; i < light_pool->count; i++) { for (size_t i = 0; i < light_pool->count; i++) {
light_t *light = &light_data[i]; light_t *light = &light_data[i];
entity_t ent = { .reg = reg, .id = light_pool->dense[i] };
uint32_t id = get_lightid (ent);
int mode = lctx->light_renderers.a[id].mode;
mat4f_t view; mat4f_t view;
mat4f_t proj; mat4f_t proj;
int mode = ST_NONE;
if (!light->position[3]) {
mode = ST_CASCADE;
} else {
if (light->direction[3] > -0.5) {
mode = ST_CUBE;
} else {
mode = ST_PLANE;
}
}
switch (mode) { switch (mode) {
default: default:
case ST_NONE: case ST_NONE:
@ -755,7 +729,8 @@ create_light_matrices (lightingctx_t *lctx)
QFV_PerspectiveCos (proj, -light->direction[3]); QFV_PerspectiveCos (proj, -light->direction[3]);
break; break;
} }
mmulf (lctx->light_mats.a[i], proj, view); mmulf (view, z_up, view);
mmulf (lctx->light_mats.a[id], proj, view);
} }
} }
@ -771,81 +746,14 @@ light_compare (const void *_li2, const void *_li1, void *_lights)
int s2 = abs ((int) l2->color[3]); int s2 = abs ((int) l2->color[3]);
if (s1 == s2) { if (s1 == s2) {
return (l1->position[3] == l2->position[3]) if (l1->position[3] == l2->position[3]) {
&& (l1->direction[3] > -0.5) == (l2->direction[3] > -0.5); return (l2->direction[3] > -0.5) - (l1->direction[3] > -0.5);
}
return l2->position[3] - l1->position[3];
} }
return s1 - s2; return s1 - s2;
} }
static VkImageView
create_view (const light_renderer_t *lr, int id, vulkan_ctx_t *ctx)
{
qfv_device_t *device = ctx->device;
qfv_devfuncs_t *dfunc = device->funcs;
VkImageViewType type = 0;
const char *viewtype = 0;
switch (lr->mode) {
case ST_NONE:
return 0;
case ST_PLANE:
type = VK_IMAGE_VIEW_TYPE_2D;
viewtype = "plane";
break;
case ST_CASCADE:
type = VK_IMAGE_VIEW_TYPE_2D_ARRAY;
viewtype = "cascade";
break;
case ST_CUBE:
type = VK_IMAGE_VIEW_TYPE_CUBE;
viewtype = "cube";
break;
}
VkImageViewCreateInfo createInfo = {
VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, 0,
0,
lr->image, type, VK_FORMAT_X8_D24_UNORM_PACK32,
{
VK_COMPONENT_SWIZZLE_IDENTITY,
VK_COMPONENT_SWIZZLE_IDENTITY,
VK_COMPONENT_SWIZZLE_IDENTITY,
VK_COMPONENT_SWIZZLE_IDENTITY,
},
{ VK_IMAGE_ASPECT_DEPTH_BIT, 0, 1, lr->layer, lr->numLayers }
};
VkImageView view;
dfunc->vkCreateImageView (device->dev, &createInfo, 0, &view);
QFV_duSetObjectName (device, VK_OBJECT_TYPE_IMAGE_VIEW, view,
va (ctx->va_ctx, "iview:shadowmap:%s:%d",
viewtype, id));
(void) viewtype;//silence unused warning when vulkan debug disabled
return view;
}
static VkFramebuffer
create_framebuffer (const light_renderer_t *lr, vulkan_ctx_t *ctx)
{
return 0;//FIXME
qfv_device_t *device = ctx->device;
qfv_devfuncs_t *dfunc = device->funcs;
VkFramebuffer framebuffer;
VkFramebufferCreateInfo cInfo = {
.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO,
.renderPass = lr->renderPass,
.attachmentCount = 1,
.pAttachments = &lr->view,
.width = lr->size,
.height = lr->size,
.layers = 1,
};
dfunc->vkCreateFramebuffer (device->dev, &cInfo, 0, &framebuffer);
return framebuffer;
}
static void static void
build_shadow_maps (lightingctx_t *lctx, vulkan_ctx_t *ctx) build_shadow_maps (lightingctx_t *lctx, vulkan_ctx_t *ctx)
{ {
@ -865,10 +773,10 @@ build_shadow_maps (lightingctx_t *lctx, vulkan_ctx_t *ctx)
int size = -1; int size = -1;
int numLayers = 0; int numLayers = 0;
int totalLayers = 0; int totalLayers = 0;
int *imageMap = alloca (numLights * sizeof (int)); int imageMap[numLights];
int *lightMap = alloca (numLights * sizeof (int)); int lightMap[numLights];
int numMaps = 0; int numMaps = 0;
mapdesc_t *maps = alloca (numLights * sizeof (mapdesc_t)); mapdesc_t maps[numLights];
for (int i = 0; i < numLights; i++) { for (int i = 0; i < numLights; i++) {
lightMap[i] = i; lightMap[i] = i;
@ -878,9 +786,10 @@ build_shadow_maps (lightingctx_t *lctx, vulkan_ctx_t *ctx)
DARRAY_RESIZE (&lctx->light_renderers, numLights); DARRAY_RESIZE (&lctx->light_renderers, numLights);
for (int i = 0; i < numLights; i++) { for (int i = 0; i < numLights; i++) {
int layers = 1; int layers = 1;
int li = lightMap[i]; auto li = lightMap[i];
__auto_type lr = &lctx->light_renderers.a[li]; auto lr = &lctx->light_renderers.a[li];
*lr = (light_renderer_t) {}; *lr = (light_renderer_t) {};
set_lightid (light_pool->dense[li], reg, li);
if (!lights[li].position[3]) { if (!lights[li].position[3]) {
if (!VectorIsZero (lights[li].direction)) { if (!VectorIsZero (lights[li].direction)) {
@ -933,8 +842,8 @@ build_shadow_maps (lightingctx_t *lctx, vulkan_ctx_t *ctx)
size = 1024; size = 1024;
for (int i = 0; i < numLights; i++) { for (int i = 0; i < numLights; i++) {
int layers = 4; int layers = 4;
int li = lightMap[i]; auto li = lightMap[i];
__auto_type lr = &lctx->light_renderers.a[li]; auto lr = &lctx->light_renderers.a[li];
if (lr->mode != ST_CASCADE) { if (lr->mode != ST_CASCADE) {
continue; continue;
@ -964,7 +873,7 @@ build_shadow_maps (lightingctx_t *lctx, vulkan_ctx_t *ctx)
if (numMaps) { if (numMaps) {
qfv_resource_t *shad = calloc (1, sizeof (qfv_resource_t) qfv_resource_t *shad = calloc (1, sizeof (qfv_resource_t)
+ numMaps * sizeof (qfv_resobj_t)); + sizeof (qfv_resobj_t[numMaps]));
lctx->shadow_resources = shad; lctx->shadow_resources = shad;
*shad = (qfv_resource_t) { *shad = (qfv_resource_t) {
.name = "shadow", .name = "shadow",
@ -999,7 +908,7 @@ build_shadow_maps (lightingctx_t *lctx, vulkan_ctx_t *ctx)
for (int i = 0; i < numLights; i++) { for (int i = 0; i < numLights; i++) {
int li = lightMap[i]; int li = lightMap[i];
__auto_type lr = &lctx->light_renderers.a[li]; auto lr = &lctx->light_renderers.a[li];
if (imageMap[li] == -1) { if (imageMap[li] == -1) {
continue; continue;
@ -1007,21 +916,19 @@ build_shadow_maps (lightingctx_t *lctx, vulkan_ctx_t *ctx)
switch (lr->numLayers) { switch (lr->numLayers) {
case 6: case 6:
lr->renderPass = lctx->renderpass_6; lr->renderpass_index = 2;
break; break;
case 4: case 4:
lr->renderPass = lctx->renderpass_4; lr->renderpass_index = 1;
break; break;
case 1: case 1:
lr->renderPass = lctx->renderpass_1; lr->renderpass_index = 0;
break; break;
default: default:
Sys_Error ("build_shadow_maps: invalid light layer count: %u", Sys_Error ("build_shadow_maps: invalid light layer count: %u",
lr->numLayers); lr->numLayers);
} }
lr->image = lctx->light_images.a[imageMap[li]]; lr->image_index = imageMap[li];
lr->view = create_view (lr, li, ctx);
lr->framebuffer = create_framebuffer(lr, ctx);
} }
Sys_MaskPrintf (SYS_lighting, Sys_MaskPrintf (SYS_lighting,
"shadow maps: %d layers in %zd images: %"PRId64"\n", "shadow maps: %d layers in %zd images: %"PRId64"\n",