static void opengl_purge_shader_cache_type(const char* ext) {

	SCP_string filter("*.");
	filter += ext;

	// Previously the cache files were stored in the mod directory. Since we have a better system now, those files
	// should be cleaned out. This is only needed if we have a mod directory since otherwise we would delete the actual
	// cache files
	if (Cmdline_mod != nullptr && strlen(Cmdline_mod) > 0) {
		SCP_vector<SCP_string> cache_files;
		cf_get_file_list(cache_files, CF_TYPE_CACHE, filter.c_str(), CF_SORT_NONE, nullptr,
		                 CF_LOCATION_TYPE_PRIMARY_MOD | CF_LOCATION_TYPE_SECONDARY_MODS);

		for (auto& file : cache_files) {
			cf_delete((file + "." + ext).c_str(), CF_TYPE_CACHE,
			          CF_LOCATION_TYPE_PRIMARY_MOD | CF_LOCATION_TYPE_SECONDARY_MODS);
		}
	}

	SCP_vector<SCP_string> cache_files;
	SCP_vector<file_list_info> file_info;
	cf_get_file_list(cache_files, CF_TYPE_CACHE, filter.c_str(), CF_SORT_NONE, &file_info,
	                 CF_LOCATION_ROOT_USER | CF_LOCATION_ROOT_GAME | CF_LOCATION_TYPE_ROOT);

	Assertion(cache_files.size() == file_info.size(),
			  "cf_get_file_list returned different sizes for file names and file informations!");

	const auto TIMEOUT = 2.0 * 30.0 * 24.0 * 60.0 * 60.0; // purge timeout in seconds which is ~2 months
	const SCP_string PREFIX = "ogl_shader-";

	auto now = std::time(nullptr);
	for (size_t i = 0; i < cache_files.size(); ++i) {
		auto& name = cache_files[i];
		auto write_time = file_info[i].write_time;

		if (name.compare(0, PREFIX.size(), PREFIX) != 0) {
			// Not an OpenGL cache file
			continue;
		}

		auto diff = std::difftime(now, write_time);

		if (diff > TIMEOUT) {
			auto full_name = name + "." + ext;

			cf_delete(full_name.c_str(), CF_TYPE_CACHE);
		}
	}
}
static void opengl_purge_shader_cache_type(const char* ext) {
	SCP_vector<SCP_string> cache_files;
	SCP_vector<file_list_info> file_info;

	SCP_string filter("*.");
	filter += ext;

	cf_get_file_list(cache_files, CF_TYPE_CACHE, filter.c_str(), false, &file_info);

	Assertion(cache_files.size() == file_info.size(),
			  "cf_get_file_list returned different sizes for file names and file informations!");

	const auto TIMEOUT = 2.0 * 30.0 * 24.0 * 60.0 * 60.0; // purge timeout in seconds which is ~2 months
	const SCP_string PREFIX = "ogl_shader-";

	auto now = std::time(nullptr);
	for (size_t i = 0; i < cache_files.size(); ++i) {
		auto& name = cache_files[i];
		auto write_time = file_info[i].write_time;

		if (name.compare(0, PREFIX.size(), PREFIX) != 0) {
			// Not an OpenGL cache file
			continue;
		}

		std::cout << std::put_time(localtime(&write_time), "%c %Z") << std::endl;

		auto diff = std::difftime(now, write_time);

		if (diff > TIMEOUT) {
			auto full_name = name + "." + ext;

			cf_delete(full_name.c_str(), CF_TYPE_CACHE);
		}
	}
}
void opengl_set_object_label(GLenum type, GLuint handle, const SCP_string& name) {
	if (GLAD_GL_KHR_debug) {
		glObjectLabel(type, handle, (GLsizei) name.size(), name.c_str());
	}
}
void debug_console(void (*_func)(void))
{
	int done = 0;

	while( key_inkey() ) {
		os_poll();
	}

	if ( !debug_inited ) {
		dc_init();
	}

	dc_draw(TRUE);

	while (!done) {
		// poll the os
		os_poll();

		int k = key_inkey();
		switch( k ) {

		case KEY_SHIFTED+KEY_ENTER:
		case KEY_ESC:
			done = TRUE;
			break;

		case KEY_BACKSP:
			if (!dc_command_buf.empty()) {
				dc_command_buf.erase(dc_command_buf.size() - 1);
			}
			break;

		case KEY_F3:
		case KEY_UP:
			if (last_oldcommand < (dc_history.end() - 1)) {
				++last_oldcommand;
			}

			dc_command_buf = *last_oldcommand;
			break;

		case KEY_DOWN:
			if (last_oldcommand > dc_history.begin()) {
				--last_oldcommand;
			}

			dc_command_buf = *last_oldcommand;
			break;

		case KEY_PAGEUP:
			if (dc_scroll_y > 1) {
				dc_scroll_y--;
			}
			break;

		case KEY_PAGEDOWN:
			if (dc_scroll_y < (DBROWS - DROWS)) {
				dc_scroll_y++;
			} else {
				dc_scroll_y = (DBROWS - DROWS);
			}
			break;

		case KEY_ENTER:
			dc_scroll_y = (DBROWS - DROWS);			// Set the scroll to look at the bottom
			last_oldcommand = dc_history.begin();	// Reset the last oldcommand
			lastline = 0;	// Reset the line counter

			// Clear the command line on the window, but don't print the prompt until the command has processed
			// Stuff a copy of the command line onto the history
			// Search for the command
				// If not found:
				//   abort,
				//   dc_printf("Error: Invalid or Missing command %s", cmd.c_str()), and
				//   dc_printf(dc_prompt) when ready for input
			// Call the function for that command, and strip the cmd token from the command line string
			if (dc_command_buf.empty()) {
				dc_printf("No command given.\n");
				break;
			} // Else, continue to process the cmd_line

			// z64: Thread Note: Maybe lock a mutex here to allow a previous DCF to finish/abort before starting a new one
			// z64: We'll just assume we won't be here unless a command has finished...
			dc_history.push_front(dc_command_buf);	// Push the command onto the history queue
			last_oldcommand = dc_history.begin();	// Reset oldcommand

			while (dc_history.size() > DCMDS) {
				dc_history.pop_back();			// Keep the commands less than or equal to DCMDS
			}

			dc_command_str = dc_command_buf;	// Xfer to the command string for processing
			dc_command_buf.resize(0);			// Nullify the buffer
			dc_printf("%s%s\n", dc_prompt, dc_command_str.c_str());	// Print the command w/ prompt.
			dc_draw(FALSE);					// Redraw the console without the command line.
			dc_do_command(&dc_command_str);	// Try to do the command
			break;

		default:
			// Not any of the control key codes, so it's probably a letter or number.
			ubyte c = (ubyte)key_to_ascii(k);
			if ((c != 255) && (dc_command_buf.size() < MAX_CLI_LEN)) {
				dc_command_buf.push_back(c);
			}
		}

		// Do the passed function
		if ( _func ) {
			_func();
		}

		// All done, and ready for new entry
		dc_draw(TRUE);
	}

	while( key_inkey() ) {
		os_poll();
	}
}