void gr_opengl_recompile_all_shaders(const std::function<void(size_t, size_t)>& progress_callback)
{
	for (auto sdr = GL_shader.begin(); sdr != GL_shader.end(); ++sdr)
	{
		if (progress_callback)
			progress_callback(std::distance(GL_shader.begin(), sdr), GL_shader.size());
		sdr->program.reset();
		opengl_compile_shader_actual(sdr->shader, sdr->flags, *sdr);
	}
}
void waypoint_add_list(const char *name, SCP_vector<vec3d> &vec_list)
{
	Assert(name != NULL);

	if (find_matching_waypoint_list(name) != NULL)
	{
		Warning(LOCATION, "Waypoint list '%s' already exists in this mission!  Not adding the new list...", name);
		return;
	}

	waypoint_list new_list(name);
	Waypoint_lists.push_back(new_list);
	waypoint_list *wp_list = &Waypoint_lists.back();

	wp_list->get_waypoints().reserve(vec_list.size());
	SCP_vector<vec3d>::iterator ii;
	for (ii = vec_list.begin(); ii != vec_list.end(); ++ii)
	{
		waypoint new_waypoint(&(*ii), wp_list);
		wp_list->get_waypoints().push_back(new_waypoint);
	}

	// so that masking in the other function works
	// though if you actually hit this Assert, you have other problems
	Assert(wp_list->get_waypoints().size() <= 0xffff);
}
int gamesnd_get_by_iface_name(const char* name)
{
	Assert( Snds_iface.size() <= INT_MAX );
	Assert( Snds_iface.size() == Snds_iface_handle.size() );

	int index = gamesnd_lookup_name(name, Snds_iface);

	if (index < 0)
	{
		int i = 0;
		for(SCP_vector<game_snd>::iterator snd = Snds_iface.begin(); snd != Snds_iface.end(); ++snd)
		{
			char *p = strrchr( snd->filename, '.' );
			if(p == NULL)
			{
				if(!stricmp(snd->filename, name))
				{
					index = i;
					break;
				}
			}
			else if(!strnicmp(snd->filename, name, p-snd->filename))
			{
				index = i;
				break;
			}

			i++;
		}
	}

	return index;
}
// marks a cutscene as viewable
void cutscene_mark_viewable(char *filename)
{
	char cut_file[MAX_FILENAME_LEN];
	char file[MAX_FILENAME_LEN];

	Assert(filename!=NULL);

	// strip off extension
	strcpy_s( file, filename );
	char *p = strchr( file, '.' );
	if ( p ) {
		*p = 0;
	}

	// change to lower case
	strlwr(file);
	int i = 0;
	for (SCP_vector<cutscene_info>::iterator cut = Cutscenes.begin(); cut != Cutscenes.end(); ++cut) {
		// change the cutscene file name to lower case
		strcpy_s(cut_file, cut->filename);
		strlwr(cut_file);

		// see if the stripped filename matches the cutscene filename
		if ( strstr(cut_file, file) != NULL ) {
			cut->viewable = true;
			return;
		}
		i++;
	}
}
/*
 * Update any uninitialized EnhancedSoundData in Snds
  * with hardcoded defaults for retail.
 */
void gamesnd_add_retail_default_enhanced_sound_data()
{
	int i = 0;

	for (SCP_vector<game_snd>::iterator it = Snds.begin(), end = Snds.end(); it != end; ++it, ++i)
	{
		if (it->enhanced_sound_data.priority== SND_ENHANCED_PRIORITY_INVALID)
		{
			if (i < NUM_RETAIL_GAMEPLAY_SOUNDS)
			{
				it->enhanced_sound_data.priority= Default_sound_priorities[i].priority;
			}
			else
			{
				it->enhanced_sound_data.priority= default_enhanced_sound_data.priority;
			}
		}

		if (it->enhanced_sound_data.limit < 1)
		{
			if (i < NUM_RETAIL_GAMEPLAY_SOUNDS)
			{
				it->enhanced_sound_data.limit = Default_sound_priorities[i].limit;
			}
			else
			{
				it->enhanced_sound_data.limit= default_enhanced_sound_data.limit;
			}
		}
	}
}
/**
 * Call at the close of each level (mission)
 */
void shockwave_level_close()
{
	if ( !Shockwave_inited )
		return;

	shockwave_delete_all();
	
	size_t i;

	// unload default shockwave, and erase all others
	for (i = 0; i < Shockwave_info.size(); i++) {
		if ( !i ) {
			if (Shockwave_info[i].bitmap_id >= 0)
				bm_unload( Shockwave_info[i].bitmap_id );
			else if (Shockwave_info[i].model_id >= 0)
				model_page_out_textures( Shockwave_info[i].model_id );

			continue;
		}

		if (Shockwave_info[i].bitmap_id >= 0)
			bm_release( Shockwave_info[i].bitmap_id );

		if (Shockwave_info[i].model_id >= 0)
			model_unload( Shockwave_info[i].model_id );

		Shockwave_info.erase( Shockwave_info.begin() + i );
	}

	Shockwave_inited = 0;
}
void cutscene_close()
{
	for(SCP_vector<cutscene_info>::iterator cut = Cutscenes.begin(); cut != Cutscenes.end(); ++cut)
		if(cut->description != NULL) {
			vm_free(cut->description);
			cut->description = NULL;
		}
}
Exemple #8
0
int batch_get_size()
{
    int n_to_render = 0;
    SCP_vector<batch_item>::iterator bi;

    for (bi = geometry_map.begin(); bi != geometry_map.end(); ++bi) {
        n_to_render += bi->batch.need_to_render();
    }

    for (bi = distortion_map.begin(); bi != distortion_map.end(); ++bi) {
        if ( bi->laser )
            continue;

        n_to_render += bi->batch.need_to_render();
    }

    return n_to_render * 3;
}
void cam_close()
{
	//Set Current_camera to nothing
	Current_camera = camid();
	for (auto ii = Cameras.begin(); ii != Cameras.end(); ++ii) {
		delete * ii;
	}
	Cameras.clear();
}
Exemple #10
0
void subtitles_do_frame_post_shaded(float frametime)
{
	SCP_vector<subtitle>::iterator sub;
	for(sub = Subtitles.begin(); sub != Subtitles.end(); ++sub)
	{
		if ( sub->is_post_shaded( ) )
			sub->do_frame(frametime);
	}
}
// only call from game_shutdown()!!!
void particle_close()
{
	for (SCP_vector<particle*>::iterator p = Particles.begin(); p != Particles.end(); ++p)
	{
		(*p)->signature = 0;
		delete *p;
	}
	Particles.clear();
}
/**
 * Unload the ingame sounds from memory
 */
void gamesnd_unload_gameplay_sounds()
{
	Assert( Snds.size() <= INT_MAX );
	for (SCP_vector<game_snd>::iterator gs = Snds.begin(); gs != Snds.end(); ++gs) {
		if ( gs->id != -1 ) {
			snd_unload( gs->id );
			gs->id = -1;
		}
	}
}
// function to return 0 based index of which CD a particular movie is on
// returns -1 on failure.
int cutscenes_get_cd_num( char *filename )
{
	for (SCP_vector<cutscene_info>::iterator cut = Cutscenes.begin(); cut != Cutscenes.end(); ++cut) {
		if ( !stricmp(cut->filename, filename) ) {
			return (cut->cd - 1);
		}
	}

	return -1;
}
/**
 * Unload the interface sounds from memory
 */
void gamesnd_unload_interface_sounds()
{
	Assert( Snds_iface.size() < INT_MAX );
	for (SCP_vector<game_snd>::iterator si = Snds_iface.begin(); si != Snds_iface.end(); ++si) {
		if ( si->id != -1 ) {
			snd_unload( si->id );
			si->id = -1;
			si->id_sig = -1;
		}
	}
}
/**
 * Load the interface sounds into memory
 */
void gamesnd_load_interface_sounds()
{
	if ( !Sound_enabled )
		return;

	Assert( Snds_iface.size() < INT_MAX );
	for (SCP_vector<game_snd>::iterator si = Snds_iface.begin(); si != Snds_iface.end(); ++si) {
		if ( si->filename[0] != 0 && strnicmp(si->filename, NOX("none.wav"), 4) ) {
			si->id = snd_load(&(*si));
		}
	}
}
static void find_capture_device(OpenALInformation* info)
{
	const char *user_device = os_config_read_string( "Sound", "CaptureDevice", NULL );
	const char *default_device = info->default_capture_device.c_str();

	// in case they are the same, we only want to test it once
	if ( (user_device && default_device) && !strcmp(user_device, default_device) ) {
		user_device = NULL;
	}

	for (auto& device : info->capture_devices) {
		OALdevice new_device(device.c_str());

		if (user_device && !strcmp(device.c_str(), user_device)) {
			new_device.type = OAL_DEVICE_USER;
		} else if (default_device && !strcmp(device.c_str(), default_device)) {
			new_device.type = OAL_DEVICE_DEFAULT;
		}

		CaptureDevices.push_back( new_device );
	}

	if ( CaptureDevices.empty() ) {
		return;
	}

	std::sort( CaptureDevices.begin(), CaptureDevices.end(), openal_device_sort_func );


	// for each device that we have available, try and figure out which to use
	for (size_t idx = 0; idx < CaptureDevices.size(); idx++) {
		const ALCchar *device_name = CaptureDevices[idx].device_name.c_str();

		ALCdevice *device = alcCaptureOpenDevice(device_name, 22050, AL_FORMAT_MONO8, 22050 * 2);

		if (device == NULL) {
			continue;
		}

		if (alcGetError(device) != ALC_NO_ERROR) {
			alcCaptureCloseDevice(device);
			continue;
		}

		// ok, we should be good with this one
		Capture_device = CaptureDevices[idx].device_name;

		alcCaptureCloseDevice(device);

		break;
	}
}
// ============================== IMPLEMENTATIONS =============================
void dc_do_command(SCP_string *cmd_str)
{
	/**
	 * Grab the first word from the cmd_str
	 *  If it is not a literal, ignore it "Invalid keyword: %s"
	 *  Search for the command...
	 *      Compare the word against valid commands
	 *      If command not found, ignore it "Invalid or unknown command: %s"\
	 *  Process the command...
	 *      Call the function to process the command (the rest of the command line is in the parser)
	 *          Function takes care of long_help and status depending on the mode.
	 */
	SCP_string command;
	extern SCP_vector<debug_command*> dc_commands;	// z64: I don't like this extern here, at all. Nope nope nope.

	if (cmd_str->empty()) {
		return;
	}

	dc_parse_init(*cmd_str);

	dc_stuff_string_white(command);		// Grab the first token, presumably this is a command

	SCP_vector<debug_command*>::iterator it = find_if(dc_commands.begin(), dc_commands.end(), is_dcmd(command.c_str()));

	if (it == dc_commands.end()) {
		dc_printf("Command not found: '%s'\n", command.c_str());
		return;
	} // Else, we found our command

	try {
		(*it)->func();	// Run the command!
	
	} catch (errParseString err) {
		dc_printf("Require string(s) not found: \n");
		for (int i = 0; i < err.expected_tokens.size(); ++i) {
			dc_printf("%i: %s\n", err.expected_tokens[i].c_str());
		}

		dc_printf("Found '%s' instead\n", err.found_token.c_str());
	
	} catch (errParse err) {
		dc_printf("Invalid argument. Expected %s, found '%s'\n", token_str[err.expected_type], err.found_token.c_str());

	}

	// dc_maybe_stuff_string is vulnerable to overflow. Once the errParseOverflow throw class (or w/e) gets
	// implemented, this last command should be put into its own try{} catch{} block.
	if (dc_maybe_stuff_string(command)) {
		dc_printf( "Ignoring the unused command line tail '%s'\n", command.c_str() );
	}
}
// kill all active particles
void particle_kill_all()
{
	// kill all active particles
	Num_particles = 0;
	Num_particles_hwm = 0;

	for (SCP_vector<particle*>::iterator p = Particles.begin(); p != Particles.end(); ++p)
	{
		(*p)->signature = 0;
		delete *p;
	}
	Particles.clear();
}
Exemple #19
0
void batch_load_buffer_distortion_map_bitmaps(effect_vertex* buffer, int *n_verts)
{
    for (SCP_vector<batch_item>::iterator bi = distortion_map.begin(); bi != distortion_map.end(); ++bi) {

        if ( bi->laser )
            continue;

        if ( !bi->batch.need_to_render() )
            continue;

        Assert( bi->texture >= 0 );
        bi->batch.load_buffer(buffer, n_verts);
    }
}
Exemple #20
0
void os_poll()
{
	// Replay the buffered events
	auto end = buffered_events.end();
	for (auto it = buffered_events.begin(); it != end; ++it) {
		handle_sdl_event(*it);
	}
	buffered_events.clear();

	SDL_Event event;

	while (SDL_PollEvent(&event)) {
		handle_sdl_event(event);
	}
}
/**
 * Load the ingame sounds into memory
 */
void gamesnd_load_gameplay_sounds()
{
	if ( !Sound_enabled )
		return;

	Assert( Snds.size() <= INT_MAX );
	for (SCP_vector<game_snd>::iterator gs = Snds.begin(); gs != Snds.end(); ++gs) {
		if ( gs->filename[0] != 0 && strnicmp(gs->filename, NOX("none.wav"), 4) ) {
			if ( !gs->preload ) { // don't try to load anything that's already preloaded
				game_busy( NOX("** preloading gameplay sounds **") );		// Animate loading cursor... does nothing if loading screen not active.
				gs->id = snd_load(&(*gs));
			}
		}
	}
}
void cutscenes_screen_init()
{
	int i;
	ui_button_info *b;

	Ui_window.create(0, 0, gr_screen.max_w_unscaled, gr_screen.max_h_unscaled, 0);
	Ui_window.set_mask_bmap(Cutscene_mask_name[gr_screen.res]);

	for (i=0; i<NUM_BUTTONS; i++) {
		b = &Buttons[gr_screen.res][i];

		b->button.create(&Ui_window, "", b->x, b->y, 60, 30, (i < 2), 1);
		// set up callback for when a mouse first goes over a button
		b->button.set_highlight_action(common_play_highlight_sound);
		b->button.set_bmaps(b->filename);
		b->button.link_hotspot(b->hotspot);
	}

	// add xstrs
	for(i=0; i<NUM_CUTSCENE_TEXT; i++){
		Ui_window.add_XSTR(&Cutscene_text[gr_screen.res][i]);
	}

	Buttons[gr_screen.res][EXIT_BUTTON].button.set_hotkey(KEY_CTRLED | KEY_ENTER);
	Buttons[gr_screen.res][SCROLL_UP_BUTTON].button.set_hotkey(KEY_PAGEUP);
	Buttons[gr_screen.res][SCROLL_DOWN_BUTTON].button.set_hotkey(KEY_PAGEDOWN);	

	List_region.create(&Ui_window, "", Cutscene_list_coords[gr_screen.res][0], Cutscene_list_coords[gr_screen.res][1], Cutscene_list_coords[gr_screen.res][2], Cutscene_list_coords[gr_screen.res][3], 0, 1);
	List_region.hide();

	// set up hotkeys for buttons so we draw the correct animation frame when a key is pressed
	Buttons[gr_screen.res][SCROLL_UP_BUTTON].button.set_hotkey(KEY_PAGEUP);
	Buttons[gr_screen.res][SCROLL_DOWN_BUTTON].button.set_hotkey(KEY_PAGEDOWN);

	Background_bitmap = bm_load(Cutscene_bitmap_name[gr_screen.res]);
	Scroll_offset = Selected_line = 0;
	Description_index = -1;

    Cutscene_list.clear();
	
	int u = 0;
	for (SCP_vector<cutscene_info>::iterator cut = Cutscenes.begin(); cut != Cutscenes.end(); ++cut, u++) {
		if ( (*cut).viewable ) {
			Cutscene_list.push_back(u);
		}
	}
}
Exemple #23
0
void batch_render_lasers(bool stream_buffer)
{
    for (SCP_vector<batch_item>::iterator bi = geometry_map.begin(); bi != geometry_map.end(); ++bi) {

        if ( !bi->laser )
            continue;

        if ( !bi->batch.need_to_render() )
            continue;

        Assert( bi->texture >= 0 );
        gr_set_bitmap(bi->texture, GR_ALPHABLEND_FILTER, GR_BITBLT_MODE_NORMAL, 0.99999f);
        if ( stream_buffer ) {
            bi->batch.render_buffer(TMAP_FLAG_TEXTURED | TMAP_FLAG_XPARENT | TMAP_HTL_3D_UNLIT | TMAP_FLAG_RGB | TMAP_FLAG_GOURAUD | TMAP_FLAG_CORRECT);
        } else {
            bi->batch.render(TMAP_FLAG_TEXTURED | TMAP_FLAG_XPARENT | TMAP_HTL_3D_UNLIT | TMAP_FLAG_RGB | TMAP_FLAG_GOURAUD | TMAP_FLAG_CORRECT);
        }
    }
}
Exemple #24
0
void batch_render_geometry_map_bitmaps(bool stream_buffer)
{
    for (SCP_vector<batch_item>::iterator bi = geometry_map.begin(); bi != geometry_map.end(); ++bi) {

        if ( bi->laser )
            continue;

        if ( !bi->batch.need_to_render() )
            continue;

        Assert( bi->texture >= 0 );
        gr_set_bitmap(bi->texture, GR_ALPHABLEND_FILTER, GR_BITBLT_MODE_NORMAL, bi->alpha);
        if ( stream_buffer ) {
            bi->batch.render_buffer(bi->tmap_flags);
        } else {
            bi->batch.render( bi->tmap_flags);
        }
    }
}
/**
 * Pass a GLSL shader source to OpenGL and compile it into a usable shader object.
 * Prints compilation errors (if any) to the log.
 * Note that this will only compile shaders into objects, linking them into executables happens later
 *
 * @param shader_source		GLSL sourcecode for the shader
 * @param shader_type		OpenGL ID for the type of shader being used, like GL_FRAGMENT_SHADER_ARB, GL_VERTEX_SHADER_ARB
 * @return 					OpenGL handle for the compiled shader object
 */
GLhandleARB opengl_shader_compile_object(const SCP_vector<SCP_string>& shader_source, GLenum shader_type)
{
	GLhandleARB shader_object = 0;
	GLint status = 0;

	SCP_vector<const GLcharARB*> sources;
	sources.reserve(shader_source.size());
	for (auto it = shader_source.begin(); it != shader_source.end(); ++it) {
		sources.push_back(it->c_str());
	}

	shader_object = vglCreateShaderObjectARB(shader_type);

	vglShaderSourceARB(shader_object, sources.size(), &sources[0], NULL);
	vglCompileShaderARB(shader_object);

	// check if the compile was successful
	vglGetObjectParameterivARB(shader_object, GL_OBJECT_COMPILE_STATUS_ARB, &status);

	opengl_shader_check_info_log(shader_object);

	// we failed, bail out now...
	if (status == 0) {
		// basic error check
		mprintf(("%s shader failed to compile:\n%s\n", (shader_type == GL_VERTEX_SHADER_ARB) ? "Vertex" : ((shader_type == GL_GEOMETRY_SHADER_EXT) ? "Geometry" : "Fragment"), GLshader_info_log));

		// this really shouldn't exist, but just in case
		if (shader_object) {
			vglDeleteObjectARB(shader_object);
		}

		return 0;
	}

	// we succeeded, maybe output warnings too
	if (strlen(GLshader_info_log) > 5) {
		nprintf(("SHADER-DEBUG", "%s shader compiled with warnings:\n%s\n", (shader_type == GL_VERTEX_SHADER_ARB) ? "Vertex" : ((shader_type == GL_GEOMETRY_SHADER_EXT) ? "Geometry" : "Fragment"), GLshader_info_log));
	}

	return shader_object;
}
void dc_init(void)
{
	extern SCP_vector<debug_command*> dc_commands;

	if (debug_inited) {
		return;
	}

	debug_inited = TRUE;

	// Sort debug_commands
	std::sort(dc_commands.begin(), dc_commands.end(), dcmd_less);

	// Init window settings
	dc_font = FONT1;
	row_height = (Current_font->h) * 1.5;	// Row/Line height, in pixels
	col_width = Current_font->w;			// Col/Character width, in pixels
	dc_scroll_x = 0;
	dc_scroll_y = 0;
	DCOLS = (gr_screen.max_w / col_width) - 1;	// Subtract as needed. Windowed mode has some quirks with the resolution
	DROWS = (gr_screen.max_h / row_height) - 2;
	DBCOLS = DCOLS;
	DBROWS = 2 * DROWS;

	// Init History
	dc_history.clear();
	dc_history.push_back("");
	last_oldcommand = dc_history.begin();

	// Init buffers
	dc_buffer.clear();
	dc_buffer.push_back("");
	
	dc_command_buf.reserve(MAX_CLI_LEN);
	dc_command_buf.clear();

	sprintf(dc_title, "FreeSpace Open v%i.%i.%i", FS_VERSION_MAJOR, FS_VERSION_MINOR, FS_VERSION_BUILD);
	dc_printf("Debug console started.\n" );
}
static void process_pending_data() {
    while (!pending_frame_data.empty()) {
        auto& frame_data = pending_frame_data.front();

        bool finished;
        if (!do_gpu_queries) {
            finished = true;
        } else {
            // Determine if all queries have passed already
            finished = true;
            for (auto& trace_data : frame_data.data) {
                if (trace_data.gpu_query == -1) {
                    // Event has been processed before
                    continue;
                }

                if (gr_query_value_available(trace_data.gpu_query)) {
                    trace_data.gpu_time = gr_get_query_value(trace_data.gpu_query);
                    free_query_object(trace_data.gpu_query);
                    trace_data.gpu_query = -1;
                } else {
                    // If we are here then a query hasn't finished yet. Try again next time...
                    finished = false;
                    break;
                }
            }
        }

        if (finished) {
            std::thread writer_thread(std::bind(write_json_data, frame_data));
            writer_thread.detach();

            pending_frame_data.erase(pending_frame_data.begin());
        } else {
            // GPU queries always finish in sequence so the later queries can't be finished yet
            break;
        }
    }
}
void fs2netd_update_ban_list()
{
	int rc = 0;

	if ( !Logged_in ) {
		return;
	}

	// destroy the file prior to updating
	cf_delete( "banlist.cfg", CF_TYPE_DATA );

	do_full_packet = true;

	In_process = true;

	if (Is_standalone) {
		do { rc = fs2netd_update_ban_list_do(); } while (!rc);
	} else {
		rc = popup_till_condition(fs2netd_update_ban_list_do, XSTR("&Cancel", 779), XSTR("Requesting IP ban list", 1587));
	}

	In_process = false;
	Local_timeout = -1;

	if ( !FS2NetD_ban_list.empty() ) {
		CFILE *banlist_cfg = cfopen("banlist.cfg", "wt", CFILE_NORMAL, CF_TYPE_DATA);

		if (banlist_cfg != NULL) {
			for (SCP_vector<SCP_string>::iterator bl = FS2NetD_ban_list.begin(); bl != FS2NetD_ban_list.end(); ++bl) {
				cfputs( const_cast<char*>(bl->c_str()), banlist_cfg );
			}

			cfclose(banlist_cfg);
		}
	}

	FS2NetD_ban_list.clear();
}
/**
 * Function to search for a given game_snd with the specified name
 * in the passed vector
 *
 * @param name Name to search for
 * @param sounds Vector to seach in
 *
 */
int gamesnd_lookup_name(const char* name, const SCP_vector<game_snd>& sounds)
{
	// if we get passed -1, don't bother trying to look it up.
	if (name == NULL || *name == 0 || !strcmp(name, "-1"))
	{
		return -1;
	}

	Assert( sounds.size() <= INT_MAX );

	int i = 0;

	for(SCP_vector<game_snd>::const_iterator snd = sounds.begin(); snd != sounds.end(); ++snd)
	{
		if (!snd->name.compare(name))
		{
			return i;
		}
		i++;
	}

	return -1;
}
static void handle_includes_impl(SCP_vector<SCP_string>& include_stack,
								 SCP_stringstream& output,
								 int& include_counter,
								 const SCP_string& filename,
								 const SCP_string& original) {
	include_stack.emplace_back(filename);
	auto current_source_number = include_counter + 1;

	const char* INCLUDE_STRING = "#include";
	SCP_stringstream input(original);

	int line_num = 1;
	for (SCP_string line; std::getline(input, line);) {
		auto include_start = line.find(INCLUDE_STRING);
		if (include_start != SCP_string::npos) {
			auto first_quote = line.find('"', include_start + strlen(INCLUDE_STRING));
			auto second_quote = line.find('"', first_quote + 1);

			if (first_quote == SCP_string::npos || second_quote == SCP_string::npos) {
				Error(LOCATION,
					  "Shader %s:%d: Malformed include line. Could not find both quote charaters.",
					  filename.c_str(),
					  line_num);
			}

			auto file_name = line.substr(first_quote + 1, second_quote - first_quote - 1);
			auto existing_name = std::find_if(include_stack.begin(), include_stack.end(), [&file_name](const SCP_string& str) {
				return str == file_name;
			});
			if (existing_name != include_stack.end()) {
				SCP_stringstream stack_string;
				for (auto& name : include_stack) {
					stack_string << "\t" << name << "\n";
				}

				Error(LOCATION,
					  "Shader %s:%d: Detected cyclic include! Previous includes (top level file first):\n%s",
					  filename.c_str(),
					  line_num,
					  stack_string.str().c_str());
			}

			++include_counter;
			// The second parameter defines which source string we are currently working with. We keep track of how many
			// excludes have been in the file so far to specify this
			output << "#line 1 " << include_counter + 1 << "\n";

			handle_includes_impl(include_stack,
								 output,
								 include_counter,
								 file_name,
								 opengl_load_shader(file_name.c_str()));

			// We are done with the include file so now we can return to the original file
			output << "#line " << line_num + 1 << " " << current_source_number << "\n";
		} else {
			output << line << "\n";
		}

		++line_num;
	}

	include_stack.pop_back();
}