1 /*
2 Copyright (C) 1996-2001 Id Software, Inc.
3 Copyright (C) 2002-2009 John Fitzgibbons and others
4 Copyright (C) 2010-2014 QuakeSpasm developers
5 Copyright (C) 2016 Axel Gneiting
6 
7 This program is free software; you can redistribute it and/or
8 modify it under the terms of the GNU General Public License
9 as published by the Free Software Foundation; either version 2
10 of the License, or (at your option) any later version.
11 
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
15 
16 See the GNU General Public License for more details.
17 
18 You should have received a copy of the GNU General Public License
19 along with this program; if not, write to the Free Software
20 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
21 
22 */
23 
24 //r_alias.c -- alias model rendering
25 
26 #include "quakedef.h"
27 
28 extern cvar_t r_drawflat, gl_fullbrights, r_lerpmodels, r_lerpmove, r_showtris; //johnfitz
29 extern cvar_t scr_fov, cl_gun_fovscale;
30 
31 //up to 16 color translated skins
32 gltexture_t *playertextures[MAX_SCOREBOARD]; //johnfitz -- changed to an array of pointers
33 
34 #define NUMVERTEXNORMALS	162
35 
36 float	r_avertexnormals[NUMVERTEXNORMALS][3] =
37 {
38 #include "anorms.h"
39 };
40 
41 extern vec3_t	lightcolor; //johnfitz -- replaces "float shadelight" for lit support
42 
43 // precalculated dot products for quantized angles
44 #define SHADEDOT_QUANT 16
45 float	r_avertexnormal_dots[SHADEDOT_QUANT][256] =
46 {
47 #include "anorm_dots.h"
48 };
49 
50 extern	vec3_t			lightspot;
51 
52 float	*shadedots = r_avertexnormal_dots[0];
53 vec3_t	shadevector;
54 
55 float	entalpha; //johnfitz
56 
57 qboolean shading = true; //johnfitz -- if false, disable vertex shading for various reasons (fullbright, r_lightmap, showtris, etc)
58 
59 //johnfitz -- struct for passing lerp information to drawing functions
60 typedef struct {
61 	short pose1;
62 	short pose2;
63 	float blend;
64 	vec3_t origin;
65 	vec3_t angles;
66 } lerpdata_t;
67 //johnfitz
68 
69 typedef struct {
70 	float model_matrix[16];
71 	float shade_vector[3];
72 	float blend_factor;
73 	float light_color[3];
74 	float entalpha;
75 	unsigned int flags;
76 } aliasubo_t;
77 
78 /*
79 =============
80 GLARB_GetXYZOffset
81 
82 Returns the offset of the first vertex's meshxyz_t.xyz in the vbo for the given
83 model and pose.
84 =============
85 */
GLARB_GetXYZOffset(aliashdr_t * hdr,int pose)86 static VkDeviceSize GLARB_GetXYZOffset (aliashdr_t *hdr, int pose)
87 {
88 	const int xyzoffs = offsetof (meshxyz_t, xyz);
89 	return currententity->model->vboxyzofs + (hdr->numverts_vbo * pose * sizeof (meshxyz_t)) + xyzoffs;
90 }
91 
92 /*
93 =============
94 GL_DrawAliasFrame -- ericw
95 
96 Optimized alias model drawing codepath. This makes 1 draw call,
97 no vertex data is uploaded (it's already in the r_meshvbo and r_meshindexesvbo
98 static VBOs), and lerping and lighting is done in the vertex shader.
99 
100 Supports optional fullbright pixels.
101 
102 Based on code by MH from RMQEngine
103 =============
104 */
GL_DrawAliasFrame(aliashdr_t * paliashdr,lerpdata_t lerpdata,gltexture_t * tx,gltexture_t * fb,float model_matrix[16],float entity_alpha,qboolean alphatest)105 static void GL_DrawAliasFrame (aliashdr_t *paliashdr, lerpdata_t lerpdata, gltexture_t *tx, gltexture_t *fb, float model_matrix[16], float entity_alpha, qboolean alphatest)
106 {
107 	float	blend;
108 
109 	if (lerpdata.pose1 != lerpdata.pose2)
110 		blend = lerpdata.blend;
111 	else // poses the same means either 1. the entity has paused its animation, or 2. r_lerpmodels is disabled
112 		blend = 0;
113 
114 	vulkan_pipeline_t pipeline;
115 	if (entity_alpha >= 1.0f)
116 	{
117 		if (!alphatest)
118 			pipeline = vulkan_globals.alias_pipeline;
119 		else
120 			pipeline = vulkan_globals.alias_alphatest_pipeline;
121 	}
122 	else
123 	{
124 		if (!alphatest)
125 			pipeline = vulkan_globals.alias_blend_pipeline;
126 		else
127 			pipeline = vulkan_globals.alias_alphatest_blend_pipeline;
128 	}
129 
130 	R_BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
131 
132 	VkBuffer uniform_buffer;
133 	uint32_t uniform_offset;
134 	VkDescriptorSet ubo_set;
135 	aliasubo_t * ubo = (aliasubo_t*)R_UniformAllocate(sizeof(aliasubo_t), &uniform_buffer, &uniform_offset, &ubo_set);
136 
137 	memcpy(ubo->model_matrix, model_matrix, 16 * sizeof(float));
138 	memcpy(ubo->shade_vector, shadevector, 3 * sizeof(float));
139 	ubo->blend_factor = blend;
140 	memcpy(ubo->light_color, lightcolor, 3 * sizeof(float));
141 	ubo->flags = (fb != NULL) ? 0x1 : 0x0;
142 	if (r_fullbright_cheatsafe || r_lightmap_cheatsafe)
143 		ubo->flags |= 0x2;
144 	ubo->entalpha = entity_alpha;
145 
146 	VkDescriptorSet descriptor_sets[3] = { tx->descriptor_set, (fb != NULL) ? fb->descriptor_set : tx->descriptor_set, ubo_set };
147 	vulkan_globals.vk_cmd_bind_descriptor_sets(vulkan_globals.command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, vulkan_globals.alias_pipeline.layout.handle, 0, 3, descriptor_sets, 1, &uniform_offset);
148 
149 	VkBuffer vertex_buffers[3] = { currententity->model->vertex_buffer, currententity->model->vertex_buffer, currententity->model->vertex_buffer };
150 	VkDeviceSize vertex_offsets[3] = { (unsigned)currententity->model->vbostofs, GLARB_GetXYZOffset (paliashdr, lerpdata.pose1), GLARB_GetXYZOffset (paliashdr, lerpdata.pose2) };
151 	vulkan_globals.vk_cmd_bind_vertex_buffers(vulkan_globals.command_buffer, 0, 3, vertex_buffers, vertex_offsets);
152 	vulkan_globals.vk_cmd_bind_index_buffer(vulkan_globals.command_buffer, currententity->model->index_buffer, 0, VK_INDEX_TYPE_UINT16);
153 
154 	vulkan_globals.vk_cmd_draw_indexed(vulkan_globals.command_buffer, paliashdr->numindexes, 1, 0, 0, 0);
155 
156 	rs_aliaspasses += paliashdr->numtris;
157 }
158 
159 /*
160 =================
161 R_SetupAliasFrame -- johnfitz -- rewritten to support lerping
162 =================
163 */
R_SetupAliasFrame(aliashdr_t * paliashdr,int frame,lerpdata_t * lerpdata)164 void R_SetupAliasFrame (aliashdr_t *paliashdr, int frame, lerpdata_t *lerpdata)
165 {
166 	entity_t		*e = currententity;
167 	int				posenum, numposes;
168 
169 	if ((frame >= paliashdr->numframes) || (frame < 0))
170 	{
171 		Con_DPrintf ("R_AliasSetupFrame: no such frame %d for '%s'\n", frame, e->model->name);
172 		frame = 0;
173 	}
174 
175 	posenum = paliashdr->frames[frame].firstpose;
176 	numposes = paliashdr->frames[frame].numposes;
177 
178 	if (numposes > 1)
179 	{
180 		e->lerptime = paliashdr->frames[frame].interval;
181 		posenum += (int)(cl.time / e->lerptime) % numposes;
182 	}
183 	else
184 		e->lerptime = 0.1;
185 
186 	if (e->lerpflags & LERP_RESETANIM) //kill any lerp in progress
187 	{
188 		e->lerpstart = 0;
189 		e->previouspose = posenum;
190 		e->currentpose = posenum;
191 		e->lerpflags -= LERP_RESETANIM;
192 	}
193 	else if (e->currentpose != posenum) // pose changed, start new lerp
194 	{
195 		if (e->lerpflags & LERP_RESETANIM2) //defer lerping one more time
196 		{
197 			e->lerpstart = 0;
198 			e->previouspose = posenum;
199 			e->currentpose = posenum;
200 			e->lerpflags -= LERP_RESETANIM2;
201 		}
202 		else
203 		{
204 			e->lerpstart = cl.time;
205 			e->previouspose = e->currentpose;
206 			e->currentpose = posenum;
207 		}
208 	}
209 
210 	//set up values
211 	if (r_lerpmodels.value && !(e->model->flags & MOD_NOLERP && r_lerpmodels.value != 2))
212 	{
213 		if (e->lerpflags & LERP_FINISH && numposes == 1)
214 			lerpdata->blend = CLAMP (0, (cl.time - e->lerpstart) / (e->lerpfinish - e->lerpstart), 1);
215 		else
216 			lerpdata->blend = CLAMP (0, (cl.time - e->lerpstart) / e->lerptime, 1);
217 
218 		if (e->currentpose >= paliashdr->numposes || e->currentpose < 0)
219 		{
220 			Con_DPrintf ("R_AliasSetupFrame: invalid current pose %d (%d total) for '%s'\n", e->currentpose, paliashdr->numposes, e->model->name);
221 			e->currentpose = 0;
222 		}
223 
224 		if (e->previouspose >= paliashdr->numposes || e->previouspose < 0)
225 		{
226 			Con_DPrintf ("R_AliasSetupFrame: invalid prev pose %d (%d total) for '%s'\n", e->previouspose, paliashdr->numposes, e->model->name);
227 			e->previouspose = e->currentpose;
228 		}
229 
230 		lerpdata->pose1 = e->previouspose;
231 		lerpdata->pose2 = e->currentpose;
232 	}
233 	else //don't lerp
234 	{
235 		lerpdata->blend = 1;
236 		lerpdata->pose1 = posenum;
237 		lerpdata->pose2 = posenum;
238 	}
239 }
240 
241 /*
242 =================
243 R_SetupEntityTransform -- johnfitz -- set up transform part of lerpdata
244 =================
245 */
R_SetupEntityTransform(entity_t * e,lerpdata_t * lerpdata)246 void R_SetupEntityTransform (entity_t *e, lerpdata_t *lerpdata)
247 {
248 	float blend;
249 	vec3_t d;
250 	int i;
251 
252 	// if LERP_RESETMOVE, kill any lerps in progress
253 	if (e->lerpflags & LERP_RESETMOVE)
254 	{
255 		e->movelerpstart = 0;
256 		VectorCopy (e->origin, e->previousorigin);
257 		VectorCopy (e->origin, e->currentorigin);
258 		VectorCopy (e->angles, e->previousangles);
259 		VectorCopy (e->angles, e->currentangles);
260 		e->lerpflags -= LERP_RESETMOVE;
261 	}
262 	else if (!VectorCompare (e->origin, e->currentorigin) || !VectorCompare (e->angles, e->currentangles)) // origin/angles changed, start new lerp
263 	{
264 		e->movelerpstart = cl.time;
265 		VectorCopy (e->currentorigin, e->previousorigin);
266 		VectorCopy (e->origin,  e->currentorigin);
267 		VectorCopy (e->currentangles, e->previousangles);
268 		VectorCopy (e->angles,  e->currentangles);
269 	}
270 
271 	//set up values
272 	if (r_lerpmove.value && e != &cl.viewent && e->lerpflags & LERP_MOVESTEP)
273 	{
274 		if (e->lerpflags & LERP_FINISH)
275 			blend = CLAMP (0, (cl.time - e->movelerpstart) / (e->lerpfinish - e->movelerpstart), 1);
276 		else
277 			blend = CLAMP (0, (cl.time - e->movelerpstart) / 0.1, 1);
278 
279 		//translation
280 		VectorSubtract (e->currentorigin, e->previousorigin, d);
281 		lerpdata->origin[0] = e->previousorigin[0] + d[0] * blend;
282 		lerpdata->origin[1] = e->previousorigin[1] + d[1] * blend;
283 		lerpdata->origin[2] = e->previousorigin[2] + d[2] * blend;
284 
285 		//rotation
286 		VectorSubtract (e->currentangles, e->previousangles, d);
287 		for (i = 0; i < 3; i++)
288 		{
289 			if (d[i] > 180)  d[i] -= 360;
290 			if (d[i] < -180) d[i] += 360;
291 		}
292 		lerpdata->angles[0] = e->previousangles[0] + d[0] * blend;
293 		lerpdata->angles[1] = e->previousangles[1] + d[1] * blend;
294 		lerpdata->angles[2] = e->previousangles[2] + d[2] * blend;
295 	}
296 	else //don't lerp
297 	{
298 		VectorCopy (e->origin, lerpdata->origin);
299 		VectorCopy (e->angles, lerpdata->angles);
300 	}
301 }
302 
303 /*
304 =================
305 R_SetupAliasLighting -- johnfitz -- broken out from R_DrawAliasModel and rewritten
306 =================
307 */
R_SetupAliasLighting(entity_t * e)308 void R_SetupAliasLighting (entity_t	*e)
309 {
310 	vec3_t		dist;
311 	float		add;
312 	int			i;
313 	int		quantizedangle;
314 	float		radiansangle;
315 	vec3_t		lpos;
316 
317 	VectorCopy (e->origin, lpos);
318 	// start the light trace from slightly above the origin
319 	// this helps with models whose origin is below ground level, but are otherwise visible
320 	// (e.g. some of the candles in the DOTM start map, which would otherwise appear black)
321 	lpos[2] += e->model->maxs[2] * 0.5f;
322 	R_LightPoint (lpos, &e->lightcache);
323 
324 	//add dlights
325 	for (i=0 ; i<MAX_DLIGHTS ; i++)
326 	{
327 		if (cl_dlights[i].die >= cl.time)
328 		{
329 			VectorSubtract (currententity->origin, cl_dlights[i].origin, dist);
330 			add = cl_dlights[i].radius - VectorLength(dist);
331 			if (add > 0)
332 				VectorMA (lightcolor, add, cl_dlights[i].color, lightcolor);
333 		}
334 	}
335 
336 	// minimum light value on gun (24)
337 	if (e == &cl.viewent)
338 	{
339 		add = 72.0f - (lightcolor[0] + lightcolor[1] + lightcolor[2]);
340 		if (add > 0.0f)
341 		{
342 			lightcolor[0] += add / 3.0f;
343 			lightcolor[1] += add / 3.0f;
344 			lightcolor[2] += add / 3.0f;
345 		}
346 	}
347 
348 	// minimum light value on players (8)
349 	if (currententity > cl.entities && currententity <= cl.entities + cl.maxclients)
350 	{
351 		add = 24.0f - (lightcolor[0] + lightcolor[1] + lightcolor[2]);
352 		if (add > 0.0f)
353 		{
354 			lightcolor[0] += add / 3.0f;
355 			lightcolor[1] += add / 3.0f;
356 			lightcolor[2] += add / 3.0f;
357 		}
358 	}
359 
360 	// clamp lighting so it doesn't overbright as much (96)
361 	add = 288.0f / (lightcolor[0] + lightcolor[1] + lightcolor[2]);
362 	if (add < 1.0f)
363 		VectorScale(lightcolor, add, lightcolor);
364 
365 	quantizedangle = ((int)(e->angles[1] * (SHADEDOT_QUANT / 360.0))) & (SHADEDOT_QUANT - 1);
366 
367 //ericw -- shadevector is passed to the shader to compute shadedots inside the
368 //shader, see GLAlias_CreateShaders()
369 	radiansangle = (quantizedangle / 16.0) * 2.0 * 3.14159;
370 	shadevector[0] = cos(-radiansangle);
371 	shadevector[1] = sin(-radiansangle);
372 	shadevector[2] = 1;
373 	VectorNormalize(shadevector);
374 //ericw --
375 
376 	shadedots = r_avertexnormal_dots[quantizedangle];
377 	VectorScale (lightcolor, 1.0f / 200.0f, lightcolor);
378 }
379 
380 /*
381 =================
382 R_DrawAliasModel -- johnfitz -- almost completely rewritten
383 =================
384 */
R_DrawAliasModel(entity_t * e)385 void R_DrawAliasModel (entity_t *e)
386 {
387 	aliashdr_t	*paliashdr;
388 	int			i, anim, skinnum;
389 	gltexture_t	*tx, *fb;
390 	lerpdata_t	lerpdata;
391 	qboolean	alphatest = !!(e->model->flags & MF_HOLEY);
392 
393 	//
394 	// setup pose/lerp data -- do it first so we don't miss updates due to culling
395 	//
396 	paliashdr = (aliashdr_t *)Mod_Extradata (e->model);
397 	R_SetupAliasFrame (paliashdr, e->frame, &lerpdata);
398 	R_SetupEntityTransform (e, &lerpdata);
399 
400 	//
401 	// cull it
402 	//
403 	if (R_CullModelForEntity(e))
404 		return;
405 
406 	//
407 	// transform it
408 	//
409 	float model_matrix[16];
410 	IdentityMatrix(model_matrix);
411 	R_RotateForEntity (model_matrix, lerpdata.origin, lerpdata.angles);
412 
413 	float fovscale = 1.0f;
414 	if (e == &cl.viewent && scr_fov.value > 90.f && cl_gun_fovscale.value)
415 	{
416 		fovscale = tan(scr_fov.value * (0.5f * M_PI / 180.f));
417 		fovscale = 1.f + (fovscale - 1.f) * cl_gun_fovscale.value;
418 	}
419 
420 	float translation_matrix[16];
421 	TranslationMatrix (translation_matrix, paliashdr->scale_origin[0], paliashdr->scale_origin[1] * fovscale, paliashdr->scale_origin[2] * fovscale);
422 	MatrixMultiply(model_matrix, translation_matrix);
423 
424 	// Scale multiplied by 255 because we use UNORM instead of USCALED in the vertex shader
425 	float scale_matrix[16];
426 	ScaleMatrix (scale_matrix, paliashdr->scale[0] * 255.0f, paliashdr->scale[1] * fovscale * 255.0f, paliashdr->scale[2] * fovscale * 255.0f);
427 	MatrixMultiply(model_matrix, scale_matrix);
428 
429 	//
430 	// random stuff
431 	//
432 	shading = true;
433 
434 	//
435 	// set up for alpha blending
436 	//
437 	if (r_lightmap_cheatsafe)
438 		entalpha = 1;
439 	else
440 		entalpha = ENTALPHA_DECODE(e->alpha);
441 	if (entalpha == 0)
442 		return;
443 
444 	//
445 	// set up lighting
446 	//
447 	rs_aliaspolys += paliashdr->numtris;
448 	R_SetupAliasLighting (e);
449 
450 	//
451 	// set up textures
452 	//
453 	anim = (int)(cl.time*10) & 3;
454 	skinnum = e->skinnum;
455 	if ((skinnum >= paliashdr->numskins) || (skinnum < 0))
456 	{
457 		Con_DPrintf ("R_DrawAliasModel: no such skin # %d for '%s'\n", skinnum, e->model->name);
458 		// ericw -- display skin 0 for winquake compatibility
459 		skinnum = 0;
460 	}
461 	tx = paliashdr->gltextures[skinnum][anim];
462 	fb = paliashdr->fbtextures[skinnum][anim];
463 	if (e->colormap != vid.colormap && !gl_nocolors.value)
464 	{
465 		i = e - cl.entities;
466 		if (i >= 1 && i<=cl.maxclients )
467 		    tx = playertextures[i - 1];
468 	}
469 	if (!gl_fullbrights.value)
470 		fb = NULL;
471 
472 	if (r_fullbright_cheatsafe)
473 	{
474 		lightcolor[0] = 0.5f;
475 		lightcolor[1] = 0.5f;
476 		lightcolor[2] = 0.5f;
477 	}
478 	if (r_lightmap_cheatsafe)
479 	{
480 		tx = whitetexture;
481 		fb = NULL;
482 		lightcolor[0] = 1.0f;
483 		lightcolor[1] = 1.0f;
484 		lightcolor[2] = 1.0f;
485 	}
486 
487 	//
488 	// draw it
489 	//
490 	GL_DrawAliasFrame (paliashdr, lerpdata, tx, fb, model_matrix, entalpha, alphatest);
491 }
492 
493 //johnfitz -- values for shadow matrix
494 #define SHADOW_SKEW_X -0.7 //skew along x axis. -0.7 to mimic glquake shadows
495 #define SHADOW_SKEW_Y 0 //skew along y axis. 0 to mimic glquake shadows
496 #define SHADOW_VSCALE 0 //0=completely flat
497 #define SHADOW_HEIGHT 0.1 //how far above the floor to render the shadow
498 //johnfitz
499 
500 /*
501 =================
502 R_DrawAliasModel_ShowTris -- johnfitz
503 =================
504 */
R_DrawAliasModel_ShowTris(entity_t * e)505 void R_DrawAliasModel_ShowTris (entity_t *e)
506 {
507 	aliashdr_t	*paliashdr;
508 	lerpdata_t	lerpdata;
509 	float	blend;
510 
511 	//
512 	// setup pose/lerp data -- do it first so we don't miss updates due to culling
513 	//
514 	paliashdr = (aliashdr_t *)Mod_Extradata (e->model);
515 	R_SetupAliasFrame (paliashdr, e->frame, &lerpdata);
516 	R_SetupEntityTransform (e, &lerpdata);
517 
518 	//
519 	// cull it
520 	//
521 	if (R_CullModelForEntity(e))
522 		return;
523 
524 	//
525 	// transform it
526 	//
527 	float model_matrix[16];
528 	IdentityMatrix(model_matrix);
529 	R_RotateForEntity (model_matrix, lerpdata.origin, lerpdata.angles);
530 
531 	float fovscale = 1.0f;
532 	if (e == &cl.viewent && scr_fov.value > 90.f)
533 	{
534 		fovscale = tan(scr_fov.value * (0.5f * M_PI / 180.f));
535 		fovscale = 1.f + (fovscale - 1.f) * cl_gun_fovscale.value;
536 	}
537 
538 	float translation_matrix[16];
539 	TranslationMatrix (translation_matrix, paliashdr->scale_origin[0], paliashdr->scale_origin[1] * fovscale, paliashdr->scale_origin[2] * fovscale);
540 	MatrixMultiply(model_matrix, translation_matrix);
541 
542 	// Scale multiplied by 255 because we use UNORM instead of USCALED in the vertex shader
543 	float scale_matrix[16];
544 	ScaleMatrix (scale_matrix, paliashdr->scale[0] * 255.0f, paliashdr->scale[1] * fovscale * 255.0f, paliashdr->scale[2] * fovscale * 255.0f);
545 	MatrixMultiply(model_matrix, scale_matrix);
546 
547 	if (r_showtris.value == 1)
548 		R_BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, vulkan_globals.alias_showtris_pipeline);
549 	else
550 		R_BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, vulkan_globals.alias_showtris_depth_test_pipeline);
551 
552 	if (lerpdata.pose1 != lerpdata.pose2)
553 		blend = lerpdata.blend;
554 	else // poses the same means either 1. the entity has paused its animation, or 2. r_lerpmodels is disabled
555 		blend = 0;
556 
557 	VkBuffer uniform_buffer;
558 	uint32_t uniform_offset;
559 	VkDescriptorSet ubo_set;
560 	aliasubo_t * ubo = (aliasubo_t*)R_UniformAllocate(sizeof(aliasubo_t), &uniform_buffer, &uniform_offset, &ubo_set);
561 
562 	memcpy(ubo->model_matrix, model_matrix, 16 * sizeof(float));
563 	memset(ubo->shade_vector, 0, 3 * sizeof(float));
564 	ubo->blend_factor = blend;
565 	memset(ubo->light_color, 0, 3 * sizeof(float));
566 	ubo->entalpha = 1.0f;
567 	ubo->flags = 0;
568 
569 	VkDescriptorSet descriptor_sets[3] = { nulltexture->descriptor_set, nulltexture->descriptor_set, ubo_set };
570 	vulkan_globals.vk_cmd_bind_descriptor_sets(vulkan_globals.command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, vulkan_globals.alias_pipeline.layout.handle, 0, 3, descriptor_sets, 1, &uniform_offset);
571 
572 	VkBuffer vertex_buffers[3] = { currententity->model->vertex_buffer, currententity->model->vertex_buffer, currententity->model->vertex_buffer };
573 	VkDeviceSize vertex_offsets[3] = { (unsigned)currententity->model->vbostofs, GLARB_GetXYZOffset (paliashdr, lerpdata.pose1), GLARB_GetXYZOffset (paliashdr, lerpdata.pose2) };
574 	vulkan_globals.vk_cmd_bind_vertex_buffers(vulkan_globals.command_buffer, 0, 3, vertex_buffers, vertex_offsets);
575 	vulkan_globals.vk_cmd_bind_index_buffer(vulkan_globals.command_buffer, currententity->model->index_buffer, 0, VK_INDEX_TYPE_UINT16);
576 
577 	vulkan_globals.vk_cmd_draw_indexed(vulkan_globals.command_buffer, paliashdr->numindexes, 1, 0, 0, 0);
578 }
579