/*
	vulkan_bsp.c

	Vulkan bsp

	Copyright (C) 2012       Bill Currie <bill@taniwha.org>
	Copyright (C) 2021       Bill Currie <bill@taniwha.org>

	Author: Bill Currie <bill@taniwha.org>
	Date: 2012/1/7
	Date: 2021/1/18

	This program is free software; you can redistribute it and/or
	modify it under the terms of the GNU General Public License
	as published by the Free Software Foundation; either version 2
	of the License, or (at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

	See the GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program; if not, write to:

		Free Software Foundation, Inc.
		59 Temple Place - Suite 330
		Boston, MA  02111-1307, USA

*/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif

#ifdef HAVE_STRING_H
# include <string.h>
#endif
#ifdef HAVE_STRINGS_H
# include <strings.h>
#endif
#include <stdlib.h>

#include "qfalloca.h"

#include "QF/cvar.h"
#include "QF/darray.h"
#include "QF/image.h"
#include "QF/render.h"
#include "QF/sys.h"
#include "QF/va.h"

#include "QF/scene/entity.h"

#include "QF/Vulkan/qf_bsp.h"
#include "QF/Vulkan/qf_lightmap.h"
#include "QF/Vulkan/qf_matrices.h"
#include "QF/Vulkan/qf_texture.h"
#include "QF/Vulkan/buffer.h"
#include "QF/Vulkan/barrier.h"
#include "QF/Vulkan/command.h"
#include "QF/Vulkan/debug.h"
#include "QF/Vulkan/descriptor.h"
#include "QF/Vulkan/device.h"
#include "QF/Vulkan/image.h"
#include "QF/Vulkan/instance.h"
#include "QF/Vulkan/renderpass.h"
#include "QF/Vulkan/scrap.h"
#include "QF/Vulkan/staging.h"

#include "r_internal.h"
#include "vid_vulkan.h"

typedef struct bsp_push_constants_s {
	mat4f_t     Model;
	quat_t      fog;
	float       time;
} bsp_push_constants_t;

static const char * __attribute__((used)) bsp_pass_names[] = {
	"depth",
	"g-buffer",
	"sky",
	"turb",
};

static QFV_Subpass subpass_map[] = {
	QFV_passDepth,			// QFV_bspDepth
	QFV_passGBuffer,		// QFV_bspGBuffer
	QFV_passTranslucent,	// QFV_bspSky
	QFV_passTranslucent,	// QFV_bspTurb
};

static float identity[] = {
	1, 0, 0, 0,
	0, 1, 0, 0,
	0, 0, 1, 0,
	0, 0, 0, 1,
};

#define ALLOC_CHUNK 64

typedef struct bsppoly_s {
	uint32_t    count;
	uint32_t    indices[1];
} bsppoly_t;

#define CHAIN_SURF_F2B(surf,chain)							\
	({ 														\
		instsurf_t *inst = (surf)->instsurf;				\
		if (__builtin_expect(!inst, 1))						\
			inst = get_instsurf (bctx);						\
		inst->surface = (surf);								\
		*(chain##_tail) = inst;								\
		(chain##_tail) = &inst->tex_chain;					\
		*(chain##_tail) = 0;								\
		inst;												\
	})

#define CHAIN_SURF_B2F(surf,chain) 							\
	({	 													\
		instsurf_t *inst = (surf)->instsurf;				\
		if (__builtin_expect(!inst, 1))						\
			inst = get_instsurf (bctx);						\
		inst->surface = (surf);								\
		inst->tex_chain = (chain);							\
		(chain) = inst;										\
		inst;												\
	})

#define GET_RELEASE(type,name) \
static inline type *												\
get_##name (bspctx_t *bctx)											\
{																	\
	type       *ele;												\
	if (!bctx->free_##name##s) {									\
		int         i;												\
		bctx->free_##name##s = calloc (ALLOC_CHUNK, sizeof (type));	\
		for (i = 0; i < ALLOC_CHUNK - 1; i++)						\
			bctx->free_##name##s[i]._next = &bctx->free_##name##s[i + 1];	\
	}																\
	ele = bctx->free_##name##s;										\
	bctx->free_##name##s = ele->_next;								\
	ele->_next = 0;													\
	*bctx->name##s_tail = ele;										\
	bctx->name##s_tail = &ele->_next;								\
	return ele;														\
}																	\
static inline void													\
release_##name##s (bspctx_t *bctx)									\
{																	\
	if (bctx->name##s) {											\
		*bctx->name##s_tail = bctx->free_##name##s;					\
		bctx->free_##name##s = bctx->name##s;						\
		bctx->name##s = 0;											\
		bctx->name##s_tail = &bctx->name##s;						\
	}																\
}

GET_RELEASE (elechain_t, elechain)
GET_RELEASE (elements_t, elements)
GET_RELEASE (instsurf_t, static_instsurf)
GET_RELEASE (instsurf_t, instsurf)

static void
add_texture (texture_t *tx, vulkan_ctx_t *ctx)
{
	bspctx_t   *bctx = ctx->bsp_context;

	vulktex_t  *tex = tx->render;
	if (tex->tex) {
		DARRAY_APPEND (&bctx->texture_chains, tex);
		tex->descriptor = Vulkan_CreateTextureDescriptor (ctx, tex->tex,
														  bctx->sampler);
	}
	tex->tex_chain = 0;
	tex->tex_chain_tail = &tex->tex_chain;
	tex->elechain = 0;
	tex->elechain_tail = &tex->elechain;
}

static void
init_surface_chains (mod_brush_t *brush, vulkan_ctx_t *ctx)
{
	bspctx_t   *bctx = ctx->bsp_context;

	release_static_instsurfs (bctx);
	release_instsurfs (bctx);

	for (unsigned i = 0; i < brush->nummodelsurfaces; i++) {
		brush->surfaces[i].instsurf = get_static_instsurf (bctx);
		brush->surfaces[i].instsurf->surface = &brush->surfaces[i];
	}
}

static inline void
clear_tex_chain (vulktex_t *tex)
{
	tex->tex_chain = 0;
	tex->tex_chain_tail = &tex->tex_chain;
	tex->elechain = 0;
	tex->elechain_tail = &tex->elechain;
}

static void
clear_texture_chains (bspctx_t *bctx)
{
	for (size_t i = 0; i < bctx->texture_chains.size; i++) {
		if (!bctx->texture_chains.a[i])
			continue;
		clear_tex_chain (bctx->texture_chains.a[i]);
	}
	clear_tex_chain (r_notexture_mip->render);
	release_elechains (bctx);
	release_elementss (bctx);
	release_instsurfs (bctx);
}

void
Vulkan_ClearElements (vulkan_ctx_t *ctx)
{
	bspctx_t   *bctx = ctx->bsp_context;
	release_elechains (bctx);
	release_elementss (bctx);
}

static inline void
chain_surface (msurface_t *surf, vulkan_ctx_t *ctx)
{
	bspctx_t   *bctx = ctx->bsp_context;
	instsurf_t *is;

	if (surf->flags & SURF_DRAWSKY) {
		is = CHAIN_SURF_F2B (surf, bctx->sky_chain);
	} else if ((surf->flags & SURF_DRAWTURB)
			   || (bctx->color && bctx->color[3] < 1.0)) {
		is = CHAIN_SURF_B2F (surf, bctx->waterchain);
	} else {
		texture_t  *tx;
		vulktex_t  *tex;

		if (!surf->texinfo->texture->anim_total)
			tx = surf->texinfo->texture;
		else
			tx = R_TextureAnimation (bctx->entity, surf);
		tex = tx->render;
		is = CHAIN_SURF_F2B (surf, tex->tex_chain);
	}
	is->transform = bctx->transform;
	is->color = bctx->color;
}

static void
register_textures (mod_brush_t *brush, vulkan_ctx_t *ctx)
{
	texture_t  *tex;

	for (unsigned i = 0; i < brush->numtextures; i++) {
		tex = brush->textures[i];
		if (!tex)
			continue;
		add_texture (tex, ctx);
	}
}

static void
clear_textures (vulkan_ctx_t *ctx)
{
	bspctx_t   *bctx = ctx->bsp_context;
	bctx->texture_chains.size = 0;
}

void
Vulkan_RegisterTextures (model_t **models, int num_models, vulkan_ctx_t *ctx)
{
	int         i;
	model_t    *m;
	mod_brush_t *brush = &r_refdef.worldmodel->brush;

	clear_textures (ctx);
	init_surface_chains (brush, ctx);
	add_texture (r_notexture_mip, ctx);
	register_textures (brush, ctx);
	for (i = 0; i < num_models; i++) {
		m = models[i];
		if (!m)
			continue;
		// sub-models are done as part of the main model
		if (*m->path == '*')
			continue;
		// world has already been done, not interested in non-brush models
		if (m == r_refdef.worldmodel || m->type != mod_brush)
			continue;
		brush = &m->brush;
		brush->numsubmodels = 1; // no support for submodels in non-world model
		register_textures (brush, ctx);
	}
}

static elechain_t *
add_elechain (vulktex_t *tex, bspctx_t *bctx)
{
	elechain_t *ec;

	ec = get_elechain (bctx);
	ec->elements = get_elements (bctx);
	ec->transform = 0;
	ec->color = 0;
	*tex->elechain_tail = ec;
	tex->elechain_tail = &ec->next;
	return ec;
}

static void
count_verts_inds (model_t **models, msurface_t *surf,
				  uint32_t *verts, uint32_t *inds)
{
	*verts = surf->numedges;
	*inds = surf->numedges + 1;
}

static bsppoly_t *
build_surf_displist (model_t **models, msurface_t *surf, int base,
					 bspvert_t **vert_list)
{
	int         numverts;
	int         numindices;
	int         i;
	vec_t      *vec;
	mvertex_t  *vertices;
	medge_t    *edges;
	int        *surfedges;
	int         index;
	bspvert_t  *verts;
	bsppoly_t  *poly;
	uint32_t   *ind;
	float       s, t;
	mod_brush_t *brush;

	if (surf->model_index < 0) {
		// instance model
		brush = &models[~surf->model_index]->brush;
	} else {
		// main or sub model
		brush = &r_refdef.worldmodel->brush;
	}
	vertices  = brush->vertexes;
	edges     = brush->edges;
	surfedges = brush->surfedges;
	// create a triangle fan
	numverts = surf->numedges;
	numindices = numverts + 1;
	verts = *vert_list;
	// surf->polys is set to the next slot before the call
	poly = (bsppoly_t *) surf->polys;
	poly->count = numindices;
	for (i = 0, ind = poly->indices; i < numverts; i++) {
		*ind++ = base + i;
	}
	*ind++ = -1;	// end of primitive
	surf->polys = (glpoly_t *) poly;

	mtexinfo_t *texinfo = surf->texinfo;
	for (i = 0; i < numverts; i++) {
		index = surfedges[surf->firstedge + i];
		if (index > 0) {
			vec = vertices[edges[index].v[0]].position;
		} else {
			vec = vertices[edges[-index].v[1]].position;
		}

		s = DotProduct (vec, texinfo->vecs[0]) + texinfo->vecs[0][3];
		t = DotProduct (vec, texinfo->vecs[1]) + texinfo->vecs[1][3];
		VectorCopy (vec, verts[i].vertex);
		verts[i].vertex[3] = 1;
		verts[i].tlst[0] = s / texinfo->texture->width;
		verts[i].tlst[1] = t / texinfo->texture->height;

		//lightmap texture coordinates
		if (!surf->lightpic) {
			// sky and water textures don't have lightmaps
			verts[i].tlst[2] = 0;
			verts[i].tlst[3] = 0;
			continue;
		}
		s = DotProduct (vec, texinfo->vecs[0]) + texinfo->vecs[0][3];
		t = DotProduct (vec, texinfo->vecs[1]) + texinfo->vecs[1][3];
		s -= surf->texturemins[0];
		t -= surf->texturemins[1];
		s += surf->lightpic->rect->x * 16 + 8;
		t += surf->lightpic->rect->y * 16 + 8;
		s /= 16;
		t /= 16;
		verts[i].tlst[2] = s * surf->lightpic->size;
		verts[i].tlst[3] = t * surf->lightpic->size;
	}
	*vert_list += numverts;
	return (bsppoly_t *) &poly->indices[numindices];
}

void
Vulkan_BuildDisplayLists (model_t **models, int num_models, vulkan_ctx_t *ctx)
{
	qfv_device_t *device = ctx->device;
	qfv_devfuncs_t *dfunc = device->funcs;
	bspctx_t   *bctx = ctx->bsp_context;
	int         vertex_index_base;
	model_t    *m;
	dmodel_t   *dm;
	msurface_t *surf;
	qfv_stagebuf_t *stage;
	bspvert_t  *vertices;
	bsppoly_t  *poly;
	mod_brush_t *brush;

	// run through all surfaces, chaining them to their textures, thus
	// effectively sorting the surfaces by texture (without worrying about
	// surface order on the same texture chain).
	for (int i = 0; i < num_models; i++) {
		m = models[i];
		if (!m)
			continue;
		// sub-models are done as part of the main model
		// and non-bsp models don't have surfaces.
		if (*m->path == '*' || m->type != mod_brush)
			continue;
		brush = &m->brush;
		dm = brush->submodels;
		for (unsigned j = 0; j < brush->numsurfaces; j++) {
			vulktex_t  *tex;
			if (j == dm->firstface + dm->numfaces) {
				// move on to the next sub-model
				dm++;
				if (dm == brush->submodels + brush->numsubmodels) {
					// limit the surfaces
					// probably never hit
					Sys_Printf ("R_BuildDisplayLists: too many surfaces\n");
					brush->numsurfaces = j;
					break;
				}
			}
			surf = brush->surfaces + j;
			surf->model_index = dm - brush->submodels;
			if (!surf->model_index && m != r_refdef.worldmodel) {
				surf->model_index = -1 - i;	// instanced model
			}
			tex = surf->texinfo->texture->render;
			// append surf to the texture chain
			CHAIN_SURF_F2B (surf, tex->tex_chain);
		}
	}
	// All vertices from all brush models go into one giant vbo.
	uint32_t    vertex_count = 0;
	uint32_t    index_count = 0;
	uint32_t    poly_count = 0;
	for (size_t i = 0; i < bctx->texture_chains.size; i++) {
		vulktex_t  *tex = bctx->texture_chains.a[i];
		for (instsurf_t *is = tex->tex_chain; is; is = is->tex_chain) {
			uint32_t    verts, inds;
			count_verts_inds (models, is->surface, &verts, &inds);
			vertex_count += verts;
			index_count += inds;
			poly_count++;
		}
	}

	size_t atom = device->physDev->properties.limits.nonCoherentAtomSize;
	size_t atom_mask = atom - 1;
	size_t frames = bctx->frames.size;
	size_t index_buffer_size = index_count * frames * sizeof (uint32_t);
	size_t vertex_buffer_size = vertex_count * sizeof (bspvert_t);

	index_buffer_size = (index_buffer_size + atom_mask) & ~atom_mask;
	stage = QFV_CreateStagingBuffer (device, "bsp", vertex_buffer_size,
									 ctx->cmdpool);
	qfv_packet_t *packet = QFV_PacketAcquire (stage);
	vertices = QFV_PacketExtend (packet, vertex_buffer_size);
	vertex_index_base = 0;
	// holds all the polygon definitions: vertex indices + poly_count "end of
	// primitive" markers.
	bctx->polys = malloc ((index_count + poly_count) * sizeof (uint32_t));

	// All usable surfaces have been chained to the (base) texture they use.
	// Run through the textures, using their chains to build display maps.
	// For animated textures, if a surface is on one texture of the group, it
	// will be on all.
	poly = bctx->polys;
	int count = 0;
	for (size_t i = 0; i < bctx->texture_chains.size; i++) {
		vulktex_t  *tex;
		instsurf_t *is;

		tex = bctx->texture_chains.a[i];

		for (is = tex->tex_chain; is; is = is->tex_chain) {
			msurface_t *surf = is->surface;

			surf->polys = (glpoly_t *) poly;
			poly = build_surf_displist (models, surf, vertex_index_base,
										&vertices);
			vertex_index_base += surf->numedges;
			count++;
		}
	}
	clear_texture_chains (bctx);
	Sys_MaskPrintf (SYS_vulkan,
					"R_BuildDisplayLists: verts:%u, inds:%u, "
					"polys:%u (%d) %zd\n",
					vertex_count, index_count, poly_count, count,
					((size_t) poly - (size_t) bctx->polys) / sizeof(uint32_t));
	if (index_buffer_size > bctx->index_buffer_size) {
		if (bctx->index_buffer) {
			dfunc->vkUnmapMemory (device->dev, bctx->index_memory);
			dfunc->vkDestroyBuffer (device->dev, bctx->index_buffer, 0);
			dfunc->vkFreeMemory (device->dev, bctx->index_memory, 0);
		}
		bctx->index_buffer
			= QFV_CreateBuffer (device, index_buffer_size,
								VK_BUFFER_USAGE_TRANSFER_DST_BIT
								| VK_BUFFER_USAGE_INDEX_BUFFER_BIT);
		QFV_duSetObjectName (device, VK_OBJECT_TYPE_BUFFER, bctx->index_buffer,
							 "buffer:bsp:index");
		bctx->index_memory
			= QFV_AllocBufferMemory (device, bctx->index_buffer,
									 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT,
									 index_buffer_size, 0);
		QFV_duSetObjectName (device, VK_OBJECT_TYPE_DEVICE_MEMORY,
							 bctx->index_memory, "memory:bsp:index");
		QFV_BindBufferMemory (device,
							  bctx->index_buffer, bctx->index_memory, 0);
		bctx->index_buffer_size = index_buffer_size;
		void       *data;
		dfunc->vkMapMemory (device->dev, bctx->index_memory, 0,
							index_buffer_size, 0, &data);
		uint32_t   *index_data = data;
		for (size_t i = 0; i < frames; i++) {
			uint32_t    offset = index_count * i;
			bctx->frames.a[i].index_data = index_data + offset;
			bctx->frames.a[i].index_offset = offset * sizeof (uint32_t);
			bctx->frames.a[i].index_count = 0;
		}
	}
	if (vertex_buffer_size > bctx->vertex_buffer_size) {
		if (bctx->vertex_buffer) {
			dfunc->vkDestroyBuffer (device->dev, bctx->vertex_buffer, 0);
			dfunc->vkFreeMemory (device->dev, bctx->vertex_memory, 0);
		}
		bctx->vertex_buffer
			= QFV_CreateBuffer (device, vertex_buffer_size,
								VK_BUFFER_USAGE_TRANSFER_DST_BIT
								| VK_BUFFER_USAGE_VERTEX_BUFFER_BIT);
		QFV_duSetObjectName (device, VK_OBJECT_TYPE_BUFFER,
							 bctx->vertex_buffer, "buffer:bsp:vertex");
		bctx->vertex_memory
			= QFV_AllocBufferMemory (device, bctx->vertex_buffer,
									 VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
									 vertex_buffer_size, 0);
		QFV_duSetObjectName (device, VK_OBJECT_TYPE_DEVICE_MEMORY,
							 bctx->vertex_memory, "memory:bsp:vertex");
		QFV_BindBufferMemory (device,
							  bctx->vertex_buffer, bctx->vertex_memory, 0);
		bctx->vertex_buffer_size = vertex_buffer_size;
	}

	qfv_bufferbarrier_t bb = bufferBarriers[qfv_BB_Unknown_to_TransferWrite];
	bb.barrier.buffer = bctx->vertex_buffer;
	bb.barrier.size = vertex_buffer_size;
	dfunc->vkCmdPipelineBarrier (packet->cmd, bb.srcStages, bb.dstStages,
								 0, 0, 0, 1, &bb.barrier, 0, 0);
	VkBufferCopy copy_region = { packet->offset, 0, vertex_buffer_size };
	dfunc->vkCmdCopyBuffer (packet->cmd, stage->buffer,
							bctx->vertex_buffer, 1, &copy_region);
	bb = bufferBarriers[qfv_BB_TransferWrite_to_VertexAttrRead];
	bb.barrier.buffer = bctx->vertex_buffer;
	bb.barrier.size = vertex_buffer_size;
	dfunc->vkCmdPipelineBarrier (packet->cmd, bb.srcStages, bb.dstStages,
								 0, 0, 0, 1, &bb.barrier, 0, 0);
	QFV_PacketSubmit (packet);
	QFV_DestroyStagingBuffer (stage);
}

static void
R_DrawBrushModel (entity_t *e, vulkan_ctx_t *ctx)
{
	float       dot, radius;
	model_t    *model;
	plane_t    *plane;
	msurface_t *surf;
	qboolean    rotated;
	vec3_t      mins, maxs;
	vec4f_t     org;
	mod_brush_t *brush;
	bspctx_t   *bctx = ctx->bsp_context;

	bctx->entity = e;
	bctx->transform = e->renderer.full_transform;
	bctx->color = e->renderer.colormod;

	model = e->renderer.model;
	brush = &model->brush;
	mat4f_t mat;
	Transform_GetWorldMatrix (e->transform, mat);
	memcpy (e->renderer.full_transform, mat, sizeof (mat));//FIXME
	if (mat[0][0] != 1 || mat[1][1] != 1 || mat[2][2] != 1) {
		rotated = true;
		radius = model->radius;
		if (R_CullSphere (r_refdef.frustum, &mat[3][0], radius)) { //FIXME
			return;
		}
	} else {
		rotated = false;
		VectorAdd (mat[3], model->mins, mins);
		VectorAdd (mat[3], model->maxs, maxs);
		if (R_CullBox (r_refdef.frustum, mins, maxs))
			return;
	}

	org = r_refdef.frame.position - mat[3];
	if (rotated) {
		vec4f_t     temp = org;

		org[0] = DotProduct (temp, mat[0]);
		org[1] = DotProduct (temp, mat[1]);
		org[2] = DotProduct (temp, mat[2]);
	}

	surf = &brush->surfaces[brush->firstmodelsurface];

	for (unsigned i = 0; i < brush->nummodelsurfaces; i++, surf++) {
		// find the node side on which we are
		plane = surf->plane;

		dot = PlaneDiff (org, plane);

		// enqueue the polygon
		if (((surf->flags & SURF_PLANEBACK) && (dot < -BACKFACE_EPSILON))
			|| (!(surf->flags & SURF_PLANEBACK) && (dot > BACKFACE_EPSILON))) {
			chain_surface (surf, ctx);
		}
	}
}

static inline void
visit_leaf (mleaf_t *leaf)
{
	// deal with model fragments in this leaf
	if (leaf->efrags)
		R_StoreEfrags (leaf->efrags);
}

static inline int
get_side (mnode_t *node)
{
	// find the node side on which we are
	plane_t    *plane = node->plane;
	vec4f_t     org = r_refdef.frame.position;

	if (plane->type < 3)
		return (org[plane->type] - plane->dist) < 0;
	return (DotProduct (org, plane->normal) - plane->dist) < 0;
}

static inline void
visit_node (mod_brush_t *brush, mnode_t *node, int side, vulkan_ctx_t *ctx)
{
	int         c;
	msurface_t *surf;

	// sneaky hack for side = side ? SURF_PLANEBACK : 0;
	side = (~side + 1) & SURF_PLANEBACK;
	// draw stuff
	if ((c = node->numsurfaces)) {
		surf = brush->surfaces + node->firstsurface;
		for (; c; c--, surf++) {
			if (surf->visframe != r_visframecount)
				continue;

			// side is either 0 or SURF_PLANEBACK
			if (side ^ (surf->flags & SURF_PLANEBACK))
				continue;               // wrong side

			chain_surface (surf, ctx);
		}
	}
}

static inline int
test_node (mnode_t *node)
{
	if (node->contents < 0)
		return 0;
	if (node->visframe != r_visframecount)
		return 0;
	if (R_CullBox (r_refdef.frustum, node->minmaxs, node->minmaxs + 3))
		return 0;
	return 1;
}

static void
R_VisitWorldNodes (mod_brush_t *brush, vulkan_ctx_t *ctx)
{
	typedef struct {
		mnode_t    *node;
		int         side;
	} rstack_t;
	rstack_t   *node_ptr;
	rstack_t   *node_stack;
	mnode_t    *node;
	mnode_t    *front;
	int         side;

	node = brush->nodes;
	// +2 for paranoia
	node_stack = alloca ((brush->depth + 2) * sizeof (rstack_t));
	node_ptr = node_stack;

	while (1) {
		while (test_node (node)) {
			side = get_side (node);
			front = node->children[side];
			if (test_node (front)) {
				node_ptr->node = node;
				node_ptr->side = side;
				node_ptr++;
				node = front;
				continue;
			}
			// front is either not a node (ie, is a leaf) or is not visible
			// if node is visible, then at least one of its child nodes
			// must also be visible, and a leaf child in front of the node
			// will be visible, so no need for vis checks on a leaf
			if (front->contents < 0 && front->contents != CONTENTS_SOLID)
				visit_leaf ((mleaf_t *) front);
			visit_node (brush, node, side, ctx);
			node = node->children[!side];
		}
		if (node->contents < 0 && node->contents != CONTENTS_SOLID)
			visit_leaf ((mleaf_t *) node);
		if (node_ptr != node_stack) {
			node_ptr--;
			node = node_ptr->node;
			side = node_ptr->side;
			visit_node (brush, node, side, ctx);
			node = node->children[!side];
			continue;
		}
		break;
	}
	if (node->contents < 0 && node->contents != CONTENTS_SOLID)
		visit_leaf ((mleaf_t *) node);
}

static void
push_transform (vec_t *transform, VkPipelineLayout layout,
				qfv_device_t *device, VkCommandBuffer cmd)
{
	qfv_push_constants_t push_constants[] = {
		{ VK_SHADER_STAGE_VERTEX_BIT,
			field_offset (bsp_push_constants_t, Model),
			sizeof (mat4f_t), transform },
	};
	QFV_PushConstants (device, cmd, layout, 1, push_constants);
}

static void
bind_texture (vulktex_t *tex, uint32_t setnum, VkPipelineLayout layout,
			  qfv_devfuncs_t *dfunc, VkCommandBuffer cmd)
{
	VkDescriptorSet sets[] = {
		tex->descriptor,
	};
	dfunc->vkCmdBindDescriptorSets (cmd, VK_PIPELINE_BIND_POINT_GRAPHICS,
									layout, setnum, 1, sets, 0, 0);
}

static void
push_fragconst (bsp_push_constants_t *constants, VkPipelineLayout layout,
				qfv_device_t *device, VkCommandBuffer cmd)
{
	qfv_push_constants_t push_constants[] = {
		//{ VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof (mat), mat },
		{ VK_SHADER_STAGE_FRAGMENT_BIT,
			field_offset (bsp_push_constants_t, fog),
			sizeof (constants->fog), &constants->fog },
		{ VK_SHADER_STAGE_FRAGMENT_BIT,
			field_offset (bsp_push_constants_t, time),
			sizeof (constants->time), &constants->time },
	};
	QFV_PushConstants (device, cmd, layout, 2, push_constants);
}

static void
draw_elechain (elechain_t *ec, VkPipelineLayout layout, qfv_device_t *device,
			   VkCommandBuffer cmd)
{
	qfv_devfuncs_t *dfunc = device->funcs;
	elements_t *el;

	if (ec->transform) {
		push_transform (ec->transform, layout, device, cmd);
	} else {
		//FIXME should cache current transform
		push_transform (identity, layout, device, cmd);
	}
	for (el = ec->elements; el; el = el->next) {
		if (!el->index_count)
			continue;
		dfunc->vkCmdDrawIndexed (cmd, el->index_count, 1, el->first_index,
								 0, 0);
	}
}

static void
reset_elechain (elechain_t *ec)
{
	elements_t *el;

	for (el = ec->elements; el; el = el->next) {
		el->first_index = 0;
		el->index_count = 0;
	}
}

static void
bsp_begin_subpass (QFV_BspSubpass subpass, VkPipeline pipeline,
				   VkPipelineLayout layout, qfv_renderframe_t *rFrame)
{
	vulkan_ctx_t *ctx = rFrame->vulkan_ctx;
	qfv_device_t *device = ctx->device;
	qfv_devfuncs_t *dfunc = device->funcs;
	bspctx_t   *bctx = ctx->bsp_context;
	__auto_type cframe = &ctx->frames.a[ctx->curFrame];
	bspframe_t *bframe = &bctx->frames.a[ctx->curFrame];
	VkCommandBuffer cmd = bframe->cmdSet.a[subpass];

	dfunc->vkResetCommandBuffer (cmd, 0);
	VkCommandBufferInheritanceInfo inherit = {
		VK_STRUCTURE_TYPE_COMMAND_BUFFER_INHERITANCE_INFO, 0,
		rFrame->renderpass->renderpass, subpass_map[subpass],
		cframe->framebuffer,
		0, 0, 0,
	};
	VkCommandBufferBeginInfo beginInfo = {
		VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, 0,
		VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT
		| VK_COMMAND_BUFFER_USAGE_RENDER_PASS_CONTINUE_BIT, &inherit,
	};
	dfunc->vkBeginCommandBuffer (cmd, &beginInfo);

	QFV_duCmdBeginLabel (device, cmd, va (ctx->va_ctx, "bsp:%s",
										  bsp_pass_names[subpass]),
						 {0, 0.5, 0.6, 1});

	dfunc->vkCmdBindPipeline (cmd, VK_PIPELINE_BIND_POINT_GRAPHICS,
							  pipeline);
	dfunc->vkCmdSetViewport (cmd, 0, 1, &ctx->viewport);
	dfunc->vkCmdSetScissor (cmd, 0, 1, &ctx->scissor);

	VkDeviceSize offsets[] = { 0 };
	dfunc->vkCmdBindVertexBuffers (cmd, 0, 1, &bctx->vertex_buffer, offsets);
	dfunc->vkCmdBindIndexBuffer (cmd, bctx->index_buffer, bframe->index_offset,
								 VK_INDEX_TYPE_UINT32);

	VkDescriptorSet sets[] = {
		Vulkan_Matrix_Descriptors (ctx, ctx->curFrame),
	};
	dfunc->vkCmdBindDescriptorSets (cmd, VK_PIPELINE_BIND_POINT_GRAPHICS,
									layout, 0, 1, sets, 0, 0);

	//XXX glsl_Fog_GetColor (fog);
	//XXX fog[3] = glsl_Fog_GetDensity () / 64.0;
}

static void
bsp_end_subpass (VkCommandBuffer cmd, vulkan_ctx_t *ctx)
{
	qfv_device_t *device = ctx->device;
	qfv_devfuncs_t *dfunc = device->funcs;

	QFV_duCmdEndLabel (device, cmd);
	dfunc->vkEndCommandBuffer (cmd);
}

static void
bsp_begin (qfv_renderframe_t *rFrame)
{
	vulkan_ctx_t *ctx = rFrame->vulkan_ctx;
	bspctx_t   *bctx = ctx->bsp_context;
	//XXX quat_t      fog;

	bctx->default_color[3] = 1;
	QuatCopy (bctx->default_color, bctx->last_color);

	bspframe_t *bframe = &bctx->frames.a[ctx->curFrame];

	DARRAY_APPEND (&rFrame->subpassCmdSets[QFV_passDepth],
				   bframe->cmdSet.a[QFV_bspDepth]);
	DARRAY_APPEND (&rFrame->subpassCmdSets[QFV_passGBuffer],
				   bframe->cmdSet.a[QFV_bspGBuffer]);

	qfvPushDebug (ctx, "bsp_begin_subpass");
	bsp_begin_subpass (QFV_bspDepth, bctx->depth, bctx->layout, rFrame);
	bsp_begin_subpass (QFV_bspGBuffer, bctx->gbuf, bctx->layout, rFrame);
	qfvPopDebug (ctx);
}

static void
bsp_end (vulkan_ctx_t *ctx)
{
	bspctx_t   *bctx = ctx->bsp_context;
	bspframe_t *bframe = &bctx->frames.a[ctx->curFrame];

	bsp_end_subpass (bframe->cmdSet.a[QFV_bspDepth], ctx);
	bsp_end_subpass (bframe->cmdSet.a[QFV_bspGBuffer], ctx);
}

static void
turb_begin (qfv_renderframe_t *rFrame)
{
	vulkan_ctx_t *ctx = rFrame->vulkan_ctx;
	bspctx_t   *bctx = ctx->bsp_context;

	bctx->default_color[3] = bound (0, r_wateralpha->value, 1);

	QuatCopy (bctx->default_color, bctx->last_color);

	bspframe_t *bframe = &bctx->frames.a[ctx->curFrame];

	DARRAY_APPEND (&rFrame->subpassCmdSets[QFV_passTranslucent],
				   bframe->cmdSet.a[QFV_bspTurb]);

	qfvPushDebug (ctx, "bsp_begin_subpass");
	bsp_begin_subpass (QFV_bspTurb, bctx->turb, bctx->layout, rFrame);
	qfvPopDebug (ctx);
}

static void
turb_end (vulkan_ctx_t *ctx)
{
	bspctx_t   *bctx = ctx->bsp_context;
	bspframe_t *bframe = &bctx->frames.a[ctx->curFrame];

	bsp_end_subpass (bframe->cmdSet.a[QFV_bspTurb], ctx);
}

static void
sky_begin (qfv_renderframe_t *rFrame)
{
	vulkan_ctx_t *ctx = rFrame->vulkan_ctx;
	bspctx_t   *bctx = ctx->bsp_context;

	bctx->default_color[3] = 1;
	QuatCopy (bctx->default_color, bctx->last_color);

	bspframe_t *bframe = &bctx->frames.a[ctx->curFrame];

	DARRAY_APPEND (&rFrame->subpassCmdSets[QFV_passTranslucent],
				   bframe->cmdSet.a[QFV_bspSky]);

	qfvPushDebug (ctx, "bsp_begin_subpass");
	if (bctx->skybox_tex) {
		bsp_begin_subpass (QFV_bspSky, bctx->skybox, bctx->layout, rFrame);
	} else {
		bsp_begin_subpass (QFV_bspSky, bctx->skysheet, bctx->layout, rFrame);
	}
	qfvPopDebug (ctx);
}

static void
sky_end (vulkan_ctx_t *ctx)
{
	bspctx_t   *bctx = ctx->bsp_context;
	bspframe_t *bframe = &bctx->frames.a[ctx->curFrame];

	bsp_end_subpass (bframe->cmdSet.a[QFV_bspSky], ctx);
}

static inline void
add_surf_elements (vulktex_t *tex, instsurf_t *is,
				   elechain_t **ec, elements_t **el,
				   bspctx_t *bctx, bspframe_t *bframe)
{
	bsppoly_t  *poly = (bsppoly_t *) is->surface->polys;

	if (!tex->elechain) {
		(*ec) = add_elechain (tex, bctx);
		(*ec)->transform = is->transform;
		(*ec)->color = is->color;
		(*el) = (*ec)->elements;
		(*el)->first_index = bframe->index_count;
	}
	if (is->transform != (*ec)->transform || is->color != (*ec)->color) {
		(*ec) = add_elechain (tex, bctx);
		(*ec)->transform = is->transform;
		(*ec)->color = is->color;
		(*el) = (*ec)->elements;
		(*el)->first_index = bframe->index_count;
	}
	memcpy (bframe->index_data + bframe->index_count,
			poly->indices, poly->count * sizeof (poly->indices[0]));
	(*el)->index_count += poly->count;
	bframe->index_count += poly->count;
}

static void
build_tex_elechain (vulktex_t *tex, bspctx_t *bctx, bspframe_t *bframe)
{
	instsurf_t *is;
	elechain_t *ec = 0;
	elements_t *el = 0;

	for (is = tex->tex_chain; is; is = is->tex_chain) {
		// emit the polygon indices for the surface to the texture's
		// element chain
		add_surf_elements (tex, is, &ec, &el, bctx, bframe);
	}
}

void
Vulkan_DrawWorld (qfv_renderframe_t *rFrame)
{
	vulkan_ctx_t *ctx = rFrame->vulkan_ctx;
	qfv_device_t *device = ctx->device;
	qfv_devfuncs_t *dfunc = device->funcs;
	bspctx_t   *bctx = ctx->bsp_context;
	bspframe_t *bframe = &bctx->frames.a[ctx->curFrame];
	entity_t    worldent;
	mod_brush_t *brush;

	clear_texture_chains (bctx);	// do this first for water and skys
	bframe->index_count = 0;

	memset (&worldent, 0, sizeof (worldent));
	worldent.renderer.model = r_refdef.worldmodel;
	brush = &r_refdef.worldmodel->brush;

	bctx->entity = &worldent;
	bctx->transform = 0;
	bctx->color = 0;

	R_VisitWorldNodes (brush, ctx);
	if (r_drawentities->int_val) {
		for (size_t i = 0; i < r_ent_queue->ent_queues[mod_brush].size; i++) {
			entity_t   *ent = r_ent_queue->ent_queues[mod_brush].a[i];
			R_DrawBrushModel (ent, ctx);
		}
	}

	bsp_begin (rFrame);

	push_transform (identity, bctx->layout, device,
					bframe->cmdSet.a[QFV_bspDepth]);
	push_transform (identity, bctx->layout, device,
					bframe->cmdSet.a[QFV_bspGBuffer]);
	bsp_push_constants_t frag_constants = { time: vr_data.realtime };
	push_fragconst (&frag_constants, bctx->layout, device,
					bframe->cmdSet.a[QFV_bspGBuffer]);
	for (size_t i = 0; i < bctx->texture_chains.size; i++) {
		vulktex_t  *tex;
		elechain_t *ec = 0;

		tex = bctx->texture_chains.a[i];

		build_tex_elechain (tex, bctx, bframe);

		bind_texture (tex, 1, bctx->layout, dfunc,
					  bframe->cmdSet.a[QFV_bspGBuffer]);

		for (ec = tex->elechain; ec; ec = ec->next) {
			draw_elechain (ec, bctx->layout, device,
						   bframe->cmdSet.a[QFV_bspDepth]);
			draw_elechain (ec, bctx->layout, device,
						   bframe->cmdSet.a[QFV_bspGBuffer]);
			reset_elechain (ec);
		}
		tex->elechain = 0;
		tex->elechain_tail = &tex->elechain;
	}
	bsp_end (ctx);
}

void
Vulkan_Bsp_Flush (vulkan_ctx_t *ctx)
{
	qfv_device_t *device = ctx->device;
	qfv_devfuncs_t *dfunc = device->funcs;
	bspctx_t   *bctx = ctx->bsp_context;
	bspframe_t *bframe = &bctx->frames.a[ctx->curFrame];
	size_t      atom = device->physDev->properties.limits.nonCoherentAtomSize;
	size_t      atom_mask = atom - 1;
	size_t      offset = bframe->index_offset;
	size_t      size = bframe->index_count * sizeof (uint32_t);

	offset &= ~atom_mask;
	size = (size + atom_mask) & ~atom_mask;

	VkMappedMemoryRange range = {
		VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE, 0,
		bctx->index_memory, offset, size
	};
	dfunc->vkFlushMappedMemoryRanges (device->dev, 1, &range);
}

void
Vulkan_DrawWaterSurfaces (qfv_renderframe_t *rFrame)
{
	vulkan_ctx_t *ctx = rFrame->vulkan_ctx;
	qfv_device_t *device = ctx->device;
	qfv_devfuncs_t *dfunc = device->funcs;
	bspctx_t   *bctx = ctx->bsp_context;
	bspframe_t *bframe = &bctx->frames.a[ctx->curFrame];
	instsurf_t *is;
	vulktex_t  *tex = 0;
	elechain_t *ec = 0;
	elements_t *el = 0;

	if (!bctx->waterchain)
		return;

	turb_begin (rFrame);
	push_transform (identity, bctx->layout, device,
					bframe->cmdSet.a[QFV_bspTurb]);
	bsp_push_constants_t frag_constants = { time: vr_data.realtime };
	push_fragconst (&frag_constants, bctx->layout, device,
					bframe->cmdSet.a[QFV_bspTurb]);
	for (is = bctx->waterchain; is; is = is->tex_chain) {
		msurface_t *surf = is->surface;
		if (tex != surf->texinfo->texture->render) {
			if (tex) {
				bind_texture (tex, 1, bctx->layout, dfunc,
							  bframe->cmdSet.a[QFV_bspTurb]);
				for (ec = tex->elechain; ec; ec = ec->next) {
					draw_elechain (ec, bctx->layout, device,
								   bframe->cmdSet.a[QFV_bspTurb]);
					reset_elechain (ec);
				}
				tex->elechain = 0;
				tex->elechain_tail = &tex->elechain;
			}
			tex = surf->texinfo->texture->render;
		}
		// emit the polygon indices for the surface to the texture's
		// element chain
		add_surf_elements (tex, is, &ec, &el, bctx, bframe);
	}
	if (tex) {
		bind_texture (tex, 1, bctx->layout, dfunc,
					  bframe->cmdSet.a[QFV_bspTurb]);
		for (ec = tex->elechain; ec; ec = ec->next) {
			draw_elechain (ec, bctx->layout, device,
						   bframe->cmdSet.a[QFV_bspTurb]);
			reset_elechain (ec);
		}
		tex->elechain = 0;
		tex->elechain_tail = &tex->elechain;
	}
	turb_end (ctx);

	bctx->waterchain = 0;
	bctx->waterchain_tail = &bctx->waterchain;
}

void
Vulkan_DrawSky (qfv_renderframe_t *rFrame)
{
	vulkan_ctx_t *ctx = rFrame->vulkan_ctx;
	qfv_device_t *device = ctx->device;
	qfv_devfuncs_t *dfunc = device->funcs;
	bspctx_t   *bctx = ctx->bsp_context;
	bspframe_t *bframe = &bctx->frames.a[ctx->curFrame];
	instsurf_t *is;
	vulktex_t  *tex = 0;
	elechain_t *ec = 0;
	elements_t *el = 0;

	if (!bctx->sky_chain)
		return;

	sky_begin (rFrame);
	vulktex_t skybox = { .descriptor = bctx->skybox_descriptor };
	bind_texture (&skybox, 2, bctx->layout, dfunc,
				  bframe->cmdSet.a[QFV_bspSky]);
	push_transform (identity, bctx->layout, device,
					bframe->cmdSet.a[QFV_bspSky]);
	bsp_push_constants_t frag_constants = { time: vr_data.realtime };
	push_fragconst (&frag_constants, bctx->layout, device,
					bframe->cmdSet.a[QFV_bspSky]);
	for (is = bctx->sky_chain; is; is = is->tex_chain) {
		msurface_t *surf = is->surface;
		if (tex != surf->texinfo->texture->render) {
			if (tex) {
				bind_texture (tex, 1, bctx->layout, dfunc,
							  bframe->cmdSet.a[QFV_bspSky]);
				for (ec = tex->elechain; ec; ec = ec->next) {
					draw_elechain (ec, bctx->layout, device,
								   bframe->cmdSet.a[QFV_bspSky]);
					reset_elechain (ec);
				}
				tex->elechain = 0;
				tex->elechain_tail = &tex->elechain;
			}
			tex = surf->texinfo->texture->render;
		}
		// emit the polygon indices for the surface to the texture's
		// element chain
		add_surf_elements (tex, is, &ec, &el, bctx, bframe);
	}
	if (tex) {
		bind_texture (tex, 1, bctx->layout, dfunc,
					  bframe->cmdSet.a[QFV_bspSky]);
		for (ec = tex->elechain; ec; ec = ec->next) {
			draw_elechain (ec, bctx->layout, device,
						   bframe->cmdSet.a[QFV_bspSky]);
			reset_elechain (ec);
		}
		tex->elechain = 0;
		tex->elechain_tail = &tex->elechain;
	}
	sky_end (ctx);

	bctx->sky_chain = 0;
	bctx->sky_chain_tail = &bctx->sky_chain;
}

static void
create_default_skys (vulkan_ctx_t *ctx)
{
	qfv_device_t *device = ctx->device;
	qfv_devfuncs_t *dfunc = device->funcs;
	bspctx_t   *bctx = ctx->bsp_context;
	VkImage     skybox;
	VkImage     skysheet;
	VkDeviceMemory memory;
	VkImageView boxview;
	VkImageView sheetview;

	bctx->default_skybox = calloc (2, sizeof (qfv_tex_t));
	bctx->default_skysheet = bctx->default_skybox + 1;

	VkExtent3D extents = { 1, 1, 1 };
	skybox = QFV_CreateImage (device, 1, VK_IMAGE_TYPE_2D,
							  VK_FORMAT_B8G8R8A8_UNORM, extents, 1, 1,
							  VK_SAMPLE_COUNT_1_BIT,
							  VK_IMAGE_USAGE_SAMPLED_BIT
							  | VK_IMAGE_USAGE_TRANSFER_DST_BIT);
	QFV_duSetObjectName (device, VK_OBJECT_TYPE_IMAGE, skybox,
						 "bsp:image:default_skybox");

	skysheet = QFV_CreateImage (device, 0, VK_IMAGE_TYPE_2D,
							  VK_FORMAT_B8G8R8A8_UNORM, extents, 1, 2,
							  VK_SAMPLE_COUNT_1_BIT,
							  VK_IMAGE_USAGE_SAMPLED_BIT
							  | VK_IMAGE_USAGE_TRANSFER_DST_BIT);
	QFV_duSetObjectName (device, VK_OBJECT_TYPE_IMAGE, skysheet,
						 "bsp:image:default_skysheet");

	VkMemoryRequirements requirements;
	dfunc->vkGetImageMemoryRequirements (device->dev, skybox, &requirements);
	size_t      boxsize = requirements.size;
	dfunc->vkGetImageMemoryRequirements (device->dev, skysheet, &requirements);
	size_t      sheetsize = requirements.size;

	memory = QFV_AllocImageMemory (device, skybox,
								   VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
								   boxsize + sheetsize,
								   VK_IMAGE_USAGE_TRANSFER_DST_BIT
								   | VK_IMAGE_USAGE_SAMPLED_BIT);
	QFV_duSetObjectName (device, VK_OBJECT_TYPE_DEVICE_MEMORY, memory,
						 "bsp:memory:default_skys");

	QFV_BindImageMemory (device, skybox, memory, 0);
	QFV_BindImageMemory (device, skysheet, memory, boxsize);

	boxview = QFV_CreateImageView (device, skybox, VK_IMAGE_VIEW_TYPE_CUBE,
								   VK_FORMAT_B8G8R8A8_UNORM,
								   VK_IMAGE_ASPECT_COLOR_BIT);
	QFV_duSetObjectName (device, VK_OBJECT_TYPE_IMAGE_VIEW, boxview,
						 "bsp:iview:default_skybox");

	sheetview = QFV_CreateImageView (device, skysheet,
									 VK_IMAGE_VIEW_TYPE_2D_ARRAY,
								     VK_FORMAT_B8G8R8A8_UNORM,
								     VK_IMAGE_ASPECT_COLOR_BIT);
	QFV_duSetObjectName (device, VK_OBJECT_TYPE_IMAGE_VIEW, sheetview,
						 "bsp:iview:default_skysheet");

	bctx->default_skybox->image = skybox;
	bctx->default_skybox->view = boxview;
	bctx->default_skybox->memory = memory;
	bctx->default_skysheet->image = skysheet;
	bctx->default_skysheet->view = sheetview;

	// temporarily commandeer the light map's staging buffer
	qfv_packet_t *packet = QFV_PacketAcquire (bctx->light_stage);

	qfv_imagebarrier_t ib = imageBarriers[qfv_LT_Undefined_to_TransferDst];
	ib.barrier.subresourceRange.levelCount = VK_REMAINING_MIP_LEVELS;
	ib.barrier.subresourceRange.layerCount = VK_REMAINING_ARRAY_LAYERS;
	VkImageMemoryBarrier barriers[2] = { ib.barrier, ib.barrier };
	barriers[0].image = skybox;
	barriers[1].image = skysheet;
	dfunc->vkCmdPipelineBarrier (packet->cmd, ib.srcStages, ib.dstStages,
								 0, 0, 0, 0, 0,
								 2, barriers);

	VkClearColorValue color = {};
	VkImageSubresourceRange range = {
		VK_IMAGE_ASPECT_COLOR_BIT,
		0, VK_REMAINING_MIP_LEVELS,
		0, VK_REMAINING_ARRAY_LAYERS
	};
	dfunc->vkCmdClearColorImage (packet->cmd, skybox,
								 VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
								 &color, 1, &range);
	dfunc->vkCmdClearColorImage (packet->cmd, skysheet,
								 VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
								 &color, 1, &range);

	ib = imageBarriers[qfv_LT_TransferDst_to_ShaderReadOnly];
	ib.barrier.subresourceRange.levelCount = VK_REMAINING_MIP_LEVELS;
	ib.barrier.subresourceRange.layerCount = VK_REMAINING_ARRAY_LAYERS;
	barriers[0] = ib.barrier;
	barriers[1] = ib.barrier;
	barriers[0].image = skybox;
	barriers[1].image = skysheet;
	dfunc->vkCmdPipelineBarrier (packet->cmd, ib.srcStages, ib.dstStages,
								 0, 0, 0, 0, 0,
								 2, barriers);
	QFV_PacketSubmit (packet);
}

void
Vulkan_Bsp_Init (vulkan_ctx_t *ctx)
{
	qfv_device_t *device = ctx->device;

	qfvPushDebug (ctx, "bsp init");

	bspctx_t   *bctx = calloc (1, sizeof (bspctx_t));
	ctx->bsp_context = bctx;

	bctx->waterchain_tail = &bctx->waterchain;
	bctx->sky_chain_tail = &bctx->sky_chain;
	bctx->static_instsurfs_tail = &bctx->static_instsurfs;
	bctx->elechains_tail = &bctx->elechains;
	bctx->elementss_tail = &bctx->elementss;
	bctx->instsurfs_tail = &bctx->instsurfs;

	bctx->light_scrap = QFV_CreateScrap (device, "lightmap_atlas", 2048,
										 tex_frgba, ctx->staging);
	size_t      size = QFV_ScrapSize (bctx->light_scrap);
	bctx->light_stage = QFV_CreateStagingBuffer (device, "lightmap", size,
												 ctx->cmdpool);

	create_default_skys (ctx);

	DARRAY_INIT (&bctx->texture_chains, 64);

	size_t      frames = ctx->frames.size;
	DARRAY_INIT (&bctx->frames, frames);
	DARRAY_RESIZE (&bctx->frames, frames);
	bctx->frames.grow = 0;

	bctx->depth = Vulkan_CreateGraphicsPipeline (ctx, "bsp_depth");
	bctx->gbuf = Vulkan_CreateGraphicsPipeline (ctx, "bsp_gbuf");
	bctx->skybox = Vulkan_CreateGraphicsPipeline (ctx, "bsp_skybox");
	bctx->skysheet = Vulkan_CreateGraphicsPipeline (ctx, "bsp_skysheet");
	bctx->turb = Vulkan_CreateGraphicsPipeline (ctx, "bsp_turb");
	bctx->layout = Vulkan_CreatePipelineLayout (ctx, "quakebsp_layout");
	bctx->sampler = Vulkan_CreateSampler (ctx, "quakebsp_sampler");

	for (size_t i = 0; i < frames; i++) {
		__auto_type bframe = &bctx->frames.a[i];

		DARRAY_INIT (&bframe->cmdSet, QFV_bspNumPasses);
		DARRAY_RESIZE (&bframe->cmdSet, QFV_bspNumPasses);
		bframe->cmdSet.grow = 0;

		QFV_AllocateCommandBuffers (device, ctx->cmdpool, 1, &bframe->cmdSet);

		for (int j = 0; j < QFV_bspNumPasses; j++) {
			QFV_duSetObjectName (device, VK_OBJECT_TYPE_COMMAND_BUFFER,
								 bframe->cmdSet.a[i],
								 va (ctx->va_ctx, "cmd:bsp:%zd:%s", i,
									 bsp_pass_names[j]));
		}
	}

	bctx->skybox_descriptor
		= Vulkan_CreateTextureDescriptor (ctx, bctx->default_skybox,
										  bctx->sampler);

	qfvPopDebug (ctx);
}

void
Vulkan_Bsp_Shutdown (struct vulkan_ctx_s *ctx)
{
	qfv_device_t *device = ctx->device;
	qfv_devfuncs_t *dfunc = device->funcs;
	bspctx_t   *bctx = ctx->bsp_context;

	for (size_t i = 0; i < bctx->frames.size; i++) {
		__auto_type bframe = &bctx->frames.a[i];
		free (bframe->cmdSet.a);
	}

	dfunc->vkDestroyPipeline (device->dev, bctx->depth, 0);
	dfunc->vkDestroyPipeline (device->dev, bctx->gbuf, 0);
	dfunc->vkDestroyPipeline (device->dev, bctx->skybox, 0);
	dfunc->vkDestroyPipeline (device->dev, bctx->skysheet, 0);
	dfunc->vkDestroyPipeline (device->dev, bctx->turb, 0);
	DARRAY_CLEAR (&bctx->texture_chains);
	DARRAY_CLEAR (&bctx->frames);
	QFV_DestroyStagingBuffer (bctx->light_stage);
	QFV_DestroyScrap (bctx->light_scrap);
	if (bctx->vertex_buffer) {
		dfunc->vkDestroyBuffer (device->dev, bctx->vertex_buffer, 0);
		dfunc->vkFreeMemory (device->dev, bctx->vertex_memory, 0);
	}
	if (bctx->index_buffer) {
		dfunc->vkDestroyBuffer (device->dev, bctx->index_buffer, 0);
		dfunc->vkFreeMemory (device->dev, bctx->index_memory, 0);
	}

	if (bctx->skybox_tex) {
		Vulkan_UnloadTex (ctx, bctx->skybox_tex);
	}

	dfunc->vkDestroyImageView (device->dev, bctx->default_skysheet->view, 0);
	dfunc->vkDestroyImage (device->dev, bctx->default_skysheet->image, 0);

	dfunc->vkDestroyImageView (device->dev, bctx->default_skybox->view, 0);
	dfunc->vkDestroyImage (device->dev, bctx->default_skybox->image, 0);
	dfunc->vkFreeMemory (device->dev, bctx->default_skybox->memory, 0);
	free (bctx->default_skybox);
}

static inline __attribute__((const)) int
is_pow2 (unsigned x)
{
	int         count;

	for (count = 0; x; x >>= 1)
		if (x & 1)
			count++;
	return count == 1;
}

void
Vulkan_LoadSkys (const char *sky, vulkan_ctx_t *ctx)
{
	bspctx_t   *bctx = ctx->bsp_context;

	const char *name;
	int         i;
	tex_t      *tex;
	static const char *sky_suffix[] = { "ft", "bk", "up", "dn", "rt", "lf"};

	if (bctx->skybox_tex) {
		Vulkan_UnloadTex (ctx, bctx->skybox_tex);
		Vulkan_FreeTexture (ctx, bctx->skybox_descriptor);
	}
	bctx->skybox_tex = 0;

	if (!sky || !*sky) {
		sky = r_skyname->string;
	}

	if (!*sky || !strcasecmp (sky, "none")) {
		Sys_MaskPrintf (SYS_vulkan, "Skybox unloaded\n");
		bctx->skybox_descriptor
			= Vulkan_CreateTextureDescriptor (ctx, bctx->default_skybox,
											  bctx->sampler);
		return;
	}

	name = va (ctx->va_ctx, "env/%s_map", sky);
	tex = LoadImage (name, 1);
	if (tex) {
		bctx->skybox_tex = Vulkan_LoadEnvMap (ctx, tex, sky);
		Sys_MaskPrintf (SYS_vulkan, "Loaded %s\n", name);
	} else {
		int         failed = 0;
		tex_t      *sides[6] = { };

		for (i = 0; i < 6; i++) {
			name = va (ctx->va_ctx, "env/%s%s", sky, sky_suffix[i]);
			tex = LoadImage (name, 1);
			if (!tex) {
				Sys_MaskPrintf (SYS_vulkan, "Couldn't load %s\n", name);
				// also look in gfx/env, where Darkplaces looks for skies
				name = va (ctx->va_ctx, "gfx/env/%s%s", sky, sky_suffix[i]);
				tex = LoadImage (name, 1);
				if (!tex) {
					Sys_MaskPrintf (SYS_vulkan, "Couldn't load %s\n", name);
					failed = 1;
					continue;
				}
			}
			//FIXME find a better way (also, assumes data and struct together)
			sides[i] = malloc (ImageSize (tex, 1));
			memcpy (sides[i], tex, ImageSize (tex, 1));
			sides[i]->data = (byte *)(sides[i] + 1);
			Sys_MaskPrintf (SYS_vulkan, "Loaded %s\n", name);
		}
		if (!failed) {
			bctx->skybox_tex = Vulkan_LoadEnvSides (ctx, sides, sky);
		}
		for (i = 0; i < 6; i++) {
			free (sides[i]);
		}
	}
	if (bctx->skybox_tex) {
		bctx->skybox_descriptor
			= Vulkan_CreateTextureDescriptor (ctx, bctx->skybox_tex,
											  bctx->sampler);
		Sys_MaskPrintf (SYS_vulkan, "Skybox %s loaded\n", sky);
	}
}
