void opengl_bind_vertex_array(const vertex_layout& layout) {
	auto iter = Stored_vertex_arrays.find(layout);
	if (iter != Stored_vertex_arrays.end()) {
		// Found existing vertex array!
		GL_state.BindVertexArray(iter->second);
		return;
	}

	GR_DEBUG_SCOPE("Create Vertex array");

	GLuint vao;
	glGenVertexArrays(1, &vao);
	GL_state.BindVertexArray(vao);

	for (size_t i = 0; i < layout.get_num_vertex_components(); ++i) {
		auto component = layout.get_vertex_component(i);

		auto& bind_info = GL_array_binding_data[component->format_type];
		auto& attrib_info = GL_vertex_attrib_info[bind_info.attribute_id];

		auto attribIndex = attrib_info.attribute_id;

		glEnableVertexAttribArray(attribIndex);
		glVertexAttribFormat(attribIndex,
							 bind_info.size,
							 bind_info.data_type,
							 bind_info.normalized,
							 static_cast<GLuint>(component->offset));

		// Currently, all vertex data comes from one buffer.
		glVertexAttribBinding(attribIndex, 0);
	}

	Stored_vertex_arrays.insert(std::make_pair(layout, vao));
}
size_t opengl_get_shader_idx(shader_type shader_t, unsigned int flags) 
{
	auto found = GL_shader_map.find(shader_descriptor_t(shader_t, flags));
	if (found != GL_shader_map.end()) {
		return found->second;
	}
	return GL_shader.size();
}
void obj_collide_retime_cached_pairs(int checkdly)
{
	SCP_unordered_map<uint, collider_pair>::iterator it;

	for ( it = Collision_cached_pairs.begin(); it != Collision_cached_pairs.end(); ++it ) {
		it->second.next_check_time = timestamp(checkdly);
	}
}
void opengl_delete_shader(int sdr_handle)
{
	Assert(sdr_handle >= 0);
	Assert(sdr_handle < (int)GL_shader.size());
	opengl_shader_t &victim = GL_shader[sdr_handle];
	GL_shader_map.erase(shader_descriptor_t(victim.shader, victim.flags));

	GL_shader[sdr_handle].program.reset();
	
	GL_shader[sdr_handle].flags = 0;
	GL_shader[sdr_handle].flags2 = 0;
	GL_shader[sdr_handle].shader = NUM_SHADER_TYPES;
}
void opengl_tnl_shutdown()
{
	for (auto& vao_entry : Stored_vertex_arrays) {
		glDeleteVertexArrays(1, &vao_entry.second);
	}
	Stored_vertex_arrays.clear();

	gr_opengl_deferred_shutdown();

	if ( Shadow_map_depth_texture ) {
		glDeleteTextures(1, &Shadow_map_depth_texture);
		Shadow_map_depth_texture = 0;
	}

	if ( Shadow_map_texture ) {
		glDeleteTextures(1, &Shadow_map_texture);
		Shadow_map_texture = 0;
	}

	opengl_destroy_all_buffers();
}
/**
 * Initializes the shader system. Creates a 1x1 texture that can be used as a fallback texture when framebuffer support is missing.
 * Also compiles the shaders used for particle rendering.
 */
void opengl_shader_init()
{
	glGenTextures(1,&Framebuffer_fallback_texture_id);
	GL_state.Texture.SetActiveUnit(0);
	GL_state.Texture.SetTarget(GL_TEXTURE_2D);
	GL_state.Texture.Enable(Framebuffer_fallback_texture_id);

	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
	GLuint pixels[4] = {0,0,0,0};
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 1, 1, 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, &pixels);

	GL_shader.clear();
	GL_shader_map.clear();

	// Reserve 32 shader slots. This should cover most use cases in real life.
	GL_shader.reserve(32);

	opengl_purge_old_shader_cache();

	// compile effect shaders
	gr_opengl_maybe_create_shader(SDR_TYPE_EFFECT_PARTICLE, 0);
	gr_opengl_maybe_create_shader(SDR_TYPE_EFFECT_PARTICLE, SDR_FLAG_PARTICLE_POINT_GEN);
	gr_opengl_maybe_create_shader(SDR_TYPE_EFFECT_DISTORTION, 0);

	gr_opengl_maybe_create_shader(SDR_TYPE_SHIELD_DECAL, 0);

	// compile deferred lighting shaders
	gr_opengl_maybe_create_shader(SDR_TYPE_DEFERRED_LIGHTING, 0);
	gr_opengl_maybe_create_shader(SDR_TYPE_DEFERRED_CLEAR, 0);

	// compile passthrough shader
	mprintf(("Compiling passthrough shader...\n"));
	gr_opengl_maybe_create_shader(SDR_TYPE_PASSTHROUGH_RENDER, 0);

	mprintf(("\n"));
}
/**
 * Go through GL_shader and call glDeleteObject() for all created shaders, then clear GL_shader
 */
void opengl_shader_shutdown()
{
	GL_shader.clear();
	GL_shader_map.clear();
}
void obj_reset_colliders()
{
	Collision_sort_list.clear();
	Collision_cached_pairs.clear();
}
int collide_remove_weapons( )
{
	obj_pair *opp;
	int i, num_deleted, oldest_index, j, loop_count;
	float oldest_time;

	// setup remove_weapon array.  assume we can remove it.
	for (i = 0; i < MAX_WEAPONS; i++ ) {
		if ( Weapons[i].objnum == -1 )
			crw_status[i] = CRW_NO_OBJECT;
		else
			crw_status[i] = CRW_NO_PAIR;
	}

	// first pass is to see if any of the weapons don't have collision pairs.

	if ( Cmdline_old_collision_sys ) {
		opp = &pair_used_list;
		opp = opp->next;
		while( opp != NULL )	{
			// for each collide pair, if the two objects can still collide, then set the remove_weapon
			// parameter for the weapon to 0.  need to check both parameters
			if ( opp->a->type == OBJ_WEAPON )
				crw_check_weapon( opp->a->instance, opp->next_check_time );

			if ( opp->b->type == OBJ_WEAPON )
				crw_check_weapon( opp->b->instance, opp->next_check_time );

			opp = opp->next;
		}
	} else {
		SCP_unordered_map<uint, collider_pair>::iterator it;
		collider_pair *pair_obj;

		for ( it = Collision_cached_pairs.begin(); it != Collision_cached_pairs.end(); ++it ) {
			pair_obj = &it->second;
			
			if ( !pair_obj->initialized ) {
				continue;
			}

			if ( pair_obj->a->type == OBJ_WEAPON && pair_obj->signature_a == pair_obj->a->signature ) {
				crw_check_weapon(pair_obj->a->instance, pair_obj->next_check_time);

				if ( crw_status[pair_obj->a->instance] == CRW_CAN_DELETE ) {
					pair_obj->initialized = false;
				}
			}

			if ( pair_obj->b->type == OBJ_WEAPON && pair_obj->signature_b == pair_obj->b->signature ) {
				crw_check_weapon(pair_obj->b->instance, pair_obj->next_check_time);

				if ( crw_status[pair_obj->b->instance] == CRW_CAN_DELETE ) {
					pair_obj->initialized = false;
				}
			}
		}
	}

	// for each weapon which could be removed, delete the object
	num_deleted = 0;
	for ( i = 0; i < MAX_WEAPONS; i++ ) {
		if ( crw_status[i] == CRW_CAN_DELETE ) {
			Assert( Weapons[i].objnum != -1 );
			obj_delete( Weapons[i].objnum );
			num_deleted++;
		}
	}

	if ( num_deleted )
		return num_deleted;

	// if we didn't remove any weapons, try to the N oldest weapons.  first checking for pairs, then
	// checking for oldest weapons in general.  We will go through the loop a max of 2 times.  first time
	// through, we check oldest weapons with pairs, next time through, for oldest weapons.
	loop_count = 0;
	do {
		for ( j = 0; j < CRW_MAX_TO_DELETE; j++ ) {
			oldest_time = 1000.0f;
			oldest_index = -1;
			for (i = 0; i < MAX_WEAPONS; i++ ) {
				if ( Weapons[i].objnum == -1 )			// shouldn't happen, but this is the safe thing to do.
					continue;
				if ( ((loop_count || crw_status[i] == CRW_NO_PAIR)) && (Weapons[i].lifeleft < oldest_time) ) {
					oldest_time = Weapons[i].lifeleft;
					oldest_index = i;
				}
			}
			if ( oldest_index != -1 ) {
				obj_delete(Weapons[oldest_index].objnum);
				num_deleted++;
			}
		}

		// if we deleted some weapons, then we can break
		if ( num_deleted )
			break;

		loop_count++;
	} while ( loop_count < 2);

	return num_deleted;

}