File: movie.cpp

package info (click to toggle)
freespace2 24.0.2%2Brepack-1
  • links: PTS, VCS
  • area: non-free
  • in suites: trixie
  • size: 43,188 kB
  • sloc: cpp: 583,107; ansic: 21,729; python: 1,174; sh: 464; makefile: 248; xml: 181
file content (321 lines) | stat: -rw-r--r-- 9,388 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
/*
 * Copyright (C) Volition, Inc. 1999.  All rights reserved.
 *
 * All source code herein is the property of Volition, Inc. You may not sell 
 * or otherwise commercially exploit the source or things you created based on the 
 * source.
 *
*/



#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#endif

#include "freespace.h"
#include "globalincs/pstypes.h"
#include "globalincs/alphacolors.h"
#include "globalincs/systemvars.h"
#include "io/cursor.h"
#include "graphics/2d.h"
#include "cfile/cfile.h"
#include "cmdline/cmdline.h"	
#include "cutscene/cutscenes.h" // cutscene_mark_viewable()
#include "cutscene/player.h" // cutscene_mark_viewable()
#include "tracing/categories.h"
#include "tracing/tracing.h"
#include "io/timer.h"
#include "io/key.h"
#include "mod_table/mod_table.h"
#include "network/multi.h"

extern int Game_mode;
extern int Is_standalone;

namespace {

using namespace cutscene;

struct PlaybackState {
	bool playing = true;

	vec2d posTopLeft;
	vec2d posBottomRight;

	int subtitle_font = -1;
};

void processEvents()
{
	io::mouse::CursorManager::get()->showCursor(false);

	os_remove_deferred_cutscene_key_events();
	os_poll();
}

template<typename... Args>
float print_string(float x, float y, const char* fmt, Args... params) {
	SCP_string text;
	sprintf(text, fmt, params...);

	gr_string(x, y, text.c_str(), GR_RESIZE_NONE);

	return y + font::get_current_font()->getHeight();
}

void showVideoInfo(const PlayerState& state) {
	gr_set_color_fast(&Color_white);
	font::set_font(font::FONT1);

	float y = 200.f;
	float x = 100.f;
	y = print_string(x, y, "Movie FPS: %f", state.props.fps);
	y = print_string(x, y, "Size: %dx%d", state.props.size.width, state.props.size.height);

	y = gr_screen.max_h - 200.f;

	size_t audio_queue_size = state.decoder->getAudioQueueSize();
	if (state.hasAudio) {
		ALint queued;
		OpenAL_ErrorPrint(alGetSourcei(state.audioSid, AL_BUFFERS_QUEUED, &queued));
		audio_queue_size += queued;
	}

	y = print_string(x, y, "Audio Queue size: " SIZE_T_ARG, audio_queue_size);
	y = print_string(x, y, "Video Queue size: " SIZE_T_ARG, state.decoder->getVideoQueueSize());
	y += font::get_current_font()->getHeight();
	// Estimate the size of the video buffer
	// We use YUV420p frames so one pixel uses 1.5 bytes of storage
	size_t single_frame_size = (size_t) (state.props.size.width * state.props.size.height * 1.5);
	size_t total_size = single_frame_size * state.decoder->getVideoQueueSize();
	print_string(x, y, "Video buffer size: " SIZE_T_ARG "B", total_size);
}

void displayVideo(Player* player, PlaybackState* state) {
	TRACE_SCOPE(tracing::CutsceneDrawVideoFrame);
	GR_DEBUG_SCOPE("Display Video");

	gr_clear();
	player->draw(state->posTopLeft.x, state->posTopLeft.y, state->posBottomRight.x, state->posBottomRight.y);

	auto subtitle = player->getCurrentSubtitle();
	if (!subtitle.empty() && state->subtitle_font >= 0) {
		gr_set_color_fast(&Color_bright_white);
		font::set_font(state->subtitle_font);

		int width;
		int height;
		gr_get_string_size(&width, &height, subtitle.c_str());

		// Offset the subtitle by 20% of the viewport size from the bottom
		auto y_offset = static_cast<int>((state->posBottomRight.y - state->posTopLeft.y) * 0.1);

		auto center_x = (state->posBottomRight.x - state->posTopLeft.x) / 2;

		auto text_x = center_x - width / 2;
		auto text_y = state->posBottomRight.y - y_offset - height;

		gr_string(text_x, text_y, subtitle.c_str(), GR_RESIZE_NONE);
	}

	if (Cmdline_show_video_info) {
		showVideoInfo(player->getInternalState());
	}

	gr_flip();
}
void determine_display_positions(Player* player, PlaybackState* state) {
	auto& props = player->getMovieProperties();

	float screen_ratio = (float) gr_screen.center_w / (float) gr_screen.center_h;
	float movie_ratio = (float) props.size.width / (float) props.size.height;

	float scale_by;
	if (screen_ratio > movie_ratio) {
		scale_by = (float) gr_screen.center_h / (float) props.size.height;
	} else {
		scale_by = (float) gr_screen.center_w / (float) props.size.width;
	}

	float screenX;
	float screenY;

	if (!Cmdline_noscalevid && (scale_by != 1.0f)) {
		screenX = ((gr_screen.center_w / 2.0f + gr_screen.center_offset_x) / scale_by) - (static_cast<int>(props.size.width) / 2.0f) + 0.5f;
		screenY = ((gr_screen.center_h / 2.0f + gr_screen.center_offset_y) / scale_by) - (static_cast<int>(props.size.height) / 2.0f) + 0.5f;
	} else {
		// centers on 1024x768, fills on 640x480
		screenX = i2fl(((gr_screen.center_w - static_cast<int>(props.size.width)) / 2) + gr_screen.center_offset_x);
		screenY = i2fl(((gr_screen.center_h - static_cast<int>(props.size.height)) / 2) + gr_screen.center_offset_y);
	}

	// set additional values for screen width/height and UV coords
	float screenXW = screenX + static_cast<int>(props.size.width);
	float screenYH = screenY + static_cast<int>(props.size.height);

	if (!Cmdline_noscalevid && (scale_by != 1.0f)) {
		// Apply scaling
		screenX *= scale_by;
		screenY *= scale_by;

		screenXW *= scale_by;
		screenYH *= scale_by;
	}

	state->posTopLeft.x = screenX;
	state->posTopLeft.y = screenY;

	state->posBottomRight.x = screenXW;
	state->posBottomRight.y = screenYH;
}

void initialize_player_state(Player* player, PlaybackState* state) {
	determine_display_positions(player, state);

	if (!Movie_subtitle_font.empty()) {
		state->subtitle_font = font::FontManager::getFontIndex(Movie_subtitle_font);

		if (state->subtitle_font < 0) {
			Warning(LOCATION, "Failed to load subtitle font '%s'! Subtitles will be disabled.", Movie_subtitle_font.c_str());
		}
	}
	else if (font::FontManager::numberOfFonts() > 0) {
		state->subtitle_font = 0;	// if not explicitly specified, default to the first available font
	}
}

void movie_display_loop(Player* player, PlaybackState* state) {
	// Compute the maximum time we will sleep to make sure we can still maintain the movie FPS
	// and not waste too much CPU time
	// We will sleep at most half the time a frame would be displayed
	auto sleepTime = static_cast<std::uint64_t>((1. / (4. * player->getMovieProperties().fps)) * 1000.);

	auto listener = [state](const SDL_Event& event) {
		switch (event.key.keysym.sym) {
		case SDLK_ESCAPE:
		case SDLK_KP_ENTER:
		case SDLK_RETURN:
		case SDLK_SPACE:
			state->playing = false;
			return true;
		default:
			return false;
		}
	};
	// Use a dedicated listener here since we want to listen for the KEYUP event
	auto key_handle = os::events::addEventListener(SDL_KEYUP, os::events::DEFAULT_LISTENER_WEIGHT - 10, listener);

	// slight hack to make sure that game_set_frametime() doesn't also try to cap the framerate
	auto fpsCapSave = Cmdline_NoFPSCap;
	Cmdline_NoFPSCap = 1;

	auto lastDisplayTime = timer_get_microseconds();
	while (state->playing) {
		TRACE_SCOPE(tracing::CutsceneStep);

		auto now = timer_get_microseconds();

		auto passed = now - lastDisplayTime;
		lastDisplayTime = now;

		if (player->isPlaybackReady()) {
			// Play as long as the player reports that there is more to display
			state->playing = player->update(passed);

			displayVideo(player, state);
		}

		processEvents();

		// NOTE: This does not update mission time! If movies get enabled in places
		//       other than through cutscenes then some refactoring should be done
		//       to account for normal time progression rather than just timestamps
		game_set_frametime(-1);

		// maybe do multi processing
		if ( (Game_mode & GM_MULTIPLAYER) && Net_player && (Net_player->flags & NETINFO_FLAG_DO_NETWORKING) ) {
			multi_do_frame();
		}

		if (passed < sleepTime) {
			auto sleep = sleepTime - passed;

			os_sleep(static_cast<uint>(sleep));
		}
	}

	Cmdline_NoFPSCap = fpsCapSave;

	os::events::removeEventListener(key_handle);

	// flush keys so we don't pass anything to the current state
	key_flush();
}

}

namespace movie {
// Play one movie
bool play(const char* name) {
	// mark the movie as viewable to the player when in a campaign
	// do this before anything else so that we're sure the movie is available
	// to the player even if it's not going to play right now
	if (Game_mode & GM_CAMPAIGN_MODE) {
		cutscene_mark_viewable(name);
	}

	if (Cmdline_nomovies || Is_standalone) {
		return false;
	}

	// for multiplayer, change the state in my netplayer structure
	if ( (Game_mode & GM_MULTIPLAYER) && Net_player ) {
		Net_player->state = NETPLAYER_STATE_CUTSCENE;
	}

	// clear the screen and hide the mouse cursor
	io::mouse::CursorManager::get()->pushStatus();
	io::mouse::CursorManager::get()->showCursor(false);
	Movie_active = true;
	gr_reset_clip();
	gr_set_color(255, 255, 255);
	gr_set_clear_color(0, 0, 0);
	gr_zbuffer_clear(0);
	// clear first buffer
	gr_clear();
	gr_flip();
	// clear second buffer (may not be one, but that's ok)
	gr_clear();
	gr_flip();
	// clear third buffer (may not be one, but that's ok)
	gr_clear();

	auto player = cutscene::Player::newPlayer(name);
	if (player) {
		PlaybackState state;
		initialize_player_state(player.get(), &state);

		movie_display_loop(player.get(), &state);

		player->stopPlayback();
	} else {
		// uh-oh, movie is invalid... Abory, Retry, Fail?
		mprintf(("MOVIE ERROR: Found invalid movie! (%s)\n", name));
	}

	Movie_active = false;

	// show the mouse cursor again
	io::mouse::CursorManager::get()->popStatus();

	return true;
}

void play_two(const char* name1, const char* name2) {
	if (play(name1)) {
		play(name2);
	}
}
}