Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions code/cmdline/cmdline.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -523,6 +523,10 @@ cmdline_parm frame_profile_write_file("-profile_write_file", NULL, AT_NONE); //
cmdline_parm no_unfocused_pause_arg("-no_unfocused_pause", NULL, AT_NONE); //Cmdline_no_unfocus_pause
cmdline_parm retail_time_compression_range_arg("-orig_speedx_range", NULL, AT_NONE); //Cmdline_retail_time_compression_range
cmdline_parm benchmark_mode_arg("-benchmark_mode", NULL, AT_NONE); //Cmdline_benchmark_mode
cmdline_parm headless_arg("-headless", nullptr, AT_NONE);
cmdline_parm fixed_dt_arg("-fixed-dt", "Authoritative simulation step in seconds", AT_FLOAT);
cmdline_parm max_sim_time_arg("-max-sim-time", "End after this many simulated seconds", AT_FLOAT);
cmdline_parm sim_result_arg("-sim-result", "Write the simulation result JSON to this path", AT_STRING);
cmdline_parm pilot_arg("-pilot", nullptr, AT_STRING); //Cmdline_pilot
cmdline_parm noninteractive_arg("-noninteractive", NULL, AT_NONE); //Cmdline_noninteractive
cmdline_parm json_profiling("-json_profiling", NULL, AT_NONE); //Cmdline_json_profiling
Expand Down Expand Up @@ -565,6 +569,10 @@ bool Cmdline_profile_write_file = false;
bool Cmdline_no_unfocus_pause = false;
bool Cmdline_retail_time_compression_range = false;
bool Cmdline_benchmark_mode = false;
bool Cmdline_headless = false;
float Cmdline_fixed_dt = 1.0f / 30.0f;
float Cmdline_max_sim_time = 0.0f;
char *Cmdline_sim_result = nullptr;
const char *Cmdline_pilot = nullptr;
bool Cmdline_noninteractive = false;
bool Cmdline_json_profiling = false;
Expand Down Expand Up @@ -2378,6 +2386,48 @@ bool SetCmdlineParams()
Cmdline_benchmark_mode = true;
}

if (headless_arg.found())
{
Cmdline_headless = true;
Cmdline_freespace_no_sound = 1;
Cmdline_freespace_no_music = 1;
Cmdline_nomovies = 1;
// frames run as fast as the CPU allows, on a mission clock that advances by -fixed-dt per frame
Cmdline_NoFPSCap = 1;
// nobody can dismiss an error or warning dialog, so report and exit instead (as -standalone does)
Cmdline_noninteractive = true;
}

if (fixed_dt_arg.found())
{
const float dt = fixed_dt_arg.get_float();
// a step below the fixed-point resolution (1/65536 s) would round to zero; written as a positive range
// test so that NaN is rejected too
if (!(dt >= 1.0f / 65536.0f && dt <= 0.25f)) // NOLINT(readability-simplify-boolean-expr)
{
Error(LOCATION, "-fixed-dt must be finite and in the range [1/65536, 0.25] seconds.");
return false;
}
Cmdline_fixed_dt = dt;
}

if (max_sim_time_arg.found())
{
const float max_sim_time = max_sim_time_arg.get_float();
// mission time is 16.16 fixed point, so it cannot count past 32767 seconds
if (!(max_sim_time > 0.0f && max_sim_time <= 32767.0f)) // NOLINT(readability-simplify-boolean-expr)
{
Error(LOCATION, "-max-sim-time must be finite, greater than zero and at most 32767 seconds.");
return false;
}
Cmdline_max_sim_time = max_sim_time;
}

if (sim_result_arg.found())
{
Cmdline_sim_result = sim_result_arg.str();
}

if (pilot_arg.found())
{
Cmdline_pilot = pilot_arg.str();
Expand Down
4 changes: 4 additions & 0 deletions code/cmdline/cmdline.h
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,10 @@ extern bool Cmdline_profile_write_file;
extern bool Cmdline_no_unfocus_pause;
extern bool Cmdline_retail_time_compression_range;
extern bool Cmdline_benchmark_mode;
extern bool Cmdline_headless;
extern float Cmdline_fixed_dt;
extern float Cmdline_max_sim_time;
extern char *Cmdline_sim_result;
extern const char *Cmdline_pilot;
extern bool Cmdline_noninteractive;
extern bool Cmdline_json_profiling;
Expand Down
35 changes: 26 additions & 9 deletions code/graphics/2d.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2076,7 +2076,7 @@ bool gr_init(std::unique_ptr<os::GraphicsOperations>&& graphicsOps, GraphicsAPI
if (d_mode == GraphicsAPI::Default) {
d_mode = gr_get_configured_render_api();
}
} else if ( !Is_standalone ) {
} else if ( !Is_standalone && !Cmdline_headless ) {
// We cannot continue without this, quit, but try to help the user out first
ptr = os_config_read_string(nullptr, NOX("VideocardFs2open"), nullptr);

Expand Down Expand Up @@ -2228,7 +2228,7 @@ bool gr_init(std::unique_ptr<os::GraphicsOperations>&& graphicsOps, GraphicsAPI
}

// if we are in standalone mode then just use special defaults
if (Is_standalone) {
if (Is_standalone || Cmdline_headless) {
mode = GraphicsAPI::Stub;
width = 640;
height = 480;
Expand Down Expand Up @@ -2334,17 +2334,19 @@ bool gr_init(std::unique_ptr<os::GraphicsOperations>&& graphicsOps, GraphicsAPI
gr_is_capable(gr_capability::CAPABILITY_PERSISTENT_BUFFER_MAPPING) ? "Enabled" : "Disabled"));

mprintf(("Checking mod required rendering features...\n"));
for (gr_capability ext : Required_render_ext) {
if (!gr_is_capable(ext)) {
Error(LOCATION, "Feature %s required by mod not supported by system.\n",
gr_capability_to_human_readable_string(ext).c_str());
if (!Cmdline_headless) {
for (gr_capability ext : Required_render_ext) {
if (!gr_is_capable(ext)) {
Error(LOCATION, "Feature %s required by mod not supported by system.\n",
gr_capability_to_human_readable_string(ext).c_str());
}
}
}
mprintf((" All required features are supported.\n"));

bool missing_installation = false;
if (!running_unittests && Web_cursor == nullptr) {
if (Is_standalone) {
if (Is_standalone || Cmdline_headless) {
// Cursors don't work in standalone mode, just check if the animation exists.
auto handle = bm_load_animation("cursorweb");
if (handle < 0) {
Expand Down Expand Up @@ -2372,7 +2374,7 @@ bool gr_init(std::unique_ptr<os::GraphicsOperations>&& graphicsOps, GraphicsAPI

gr_set_shader(NULL);

if (!Is_standalone) {
if (!Is_standalone && !Cmdline_headless) {
if (Using_in_game_options) {
// The value should have been loaded into the variable already so we can use that here
gr_set_gamma(Gr_gamma);
Expand Down Expand Up @@ -3296,7 +3298,7 @@ void gr_imgui_begin_frame()
ImGui::NewFrame();
}

void gr_flip(bool execute_scripting)
void gr_process_frame_callbacks(bool execute_scripting)
{
// Execute external "On Frame" work items
executor::OnFrameExecutor->process();
Expand All @@ -3311,6 +3313,11 @@ void gr_flip(bool execute_scripting)
OnFrameHook->run();
}
}
}

void gr_flip(bool execute_scripting)
{
gr_process_frame_callbacks(execute_scripting);

model_process_cached_ui_render_instances();

Expand Down Expand Up @@ -3569,6 +3576,13 @@ static graphics::util::GPUMemoryHeap* get_gpu_heap(GpuHeap heap_type) {

void gr_heap_allocate(GpuHeap heap_type, size_t size, void* data, size_t& offset_out, gr_buffer_handle& handle_out) {
TRACE_SCOPE(tracing::GpuHeapAllocate);
if (gr_screen.mode == GraphicsAPI::Stub) {
// The stub renderer has no GPU heaps. Model geometry is not rendered in
// this mode, while authoritative model and collision data remain loaded.
offset_out = 0;
handle_out = gr_buffer_handle::invalid();
return;
}

auto gpuHeap = get_gpu_heap(heap_type);

Expand All @@ -3579,6 +3593,9 @@ void gr_heap_allocate(GpuHeap heap_type, size_t size, void* data, size_t& offset
void gr_heap_deallocate(GpuHeap heap_type, size_t data_offset)
{
TRACE_SCOPE(tracing::GpuHeapDeallocate);
if (gr_screen.mode == GraphicsAPI::Stub) {
return;
}

auto gpuHeap = get_gpu_heap(heap_type);

Expand Down
3 changes: 3 additions & 0 deletions code/graphics/2d.h
Original file line number Diff line number Diff line change
Expand Up @@ -1160,6 +1160,9 @@ bool gr_is_screenshot_requested();

//#define gr_flip GR_CALL(gr_screen.gf_flip)
void gr_flip(bool execute_scripting = true);
// Run per-frame scripting work without performing presentation. Headless
// simulation calls this once after each authoritative gameplay frame.
void gr_process_frame_callbacks(bool execute_scripting = true);

inline void gr_setup_frame() {
gr_screen.gf_setup_frame();
Expand Down
28 changes: 28 additions & 0 deletions code/io/timer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ static bool Timestamp_sudo_paused = false;

static uint64_t Timestamp_microseconds_at_mission_start = 0;

static bool Simulation_clock_enabled = false;
static uint64_t Simulation_clock_microseconds = 0;
static uint64_t Simulation_clock_fraction = 0;


static uint64_t timestamp_get_raw(bool start_frame = false);

Expand Down Expand Up @@ -137,6 +141,26 @@ std::uint64_t timer_get_nanoseconds()
return static_cast<uint64_t>(time * Timer_to_nanoseconds);
}

void timer_set_simulation_clock_enabled(bool enabled)
{
Simulation_clock_enabled = enabled;
// Start well past zero, as a real session's clock is by the time a mission starts: TIMESTAMP values 0 and 1
// mean "never" and "immediate", and cooldowns measured from a zero-initialised time assume a running clock.
Simulation_clock_microseconds = 3600 * MICROSECONDS_PER_SECOND;
Simulation_clock_fraction = 0;
}

void timer_advance_simulation_clock(fix delta)
{
if (!Simulation_clock_enabled || delta <= 0) {
return;
}

const auto fixed_microseconds = static_cast<uint64_t>(delta) * MICROSECONDS_PER_SECOND + Simulation_clock_fraction;
Simulation_clock_microseconds += fixed_microseconds / F1_0;
Simulation_clock_fraction = fixed_microseconds % F1_0;
}

static uint64_t timestamp_get_raw(bool start_frame)
{
static uint64_t timestamp_raw = 0;
Expand All @@ -158,6 +182,10 @@ static uint64_t timestamp_get_raw(bool start_frame)

static uint64_t timestamp_get_microseconds()
{
if (Simulation_clock_enabled) {
return Simulation_clock_microseconds;
}

return Timestamp_time_compression_microseconds_offset + static_cast<uint64_t>(timestamp_get_raw() * Timer_to_microseconds * Timestamp_time_compression_multiplier);
}

Expand Down
6 changes: 6 additions & 0 deletions code/io/timer.h
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,12 @@ extern std::uint64_t timer_get_nanoseconds();
extern int timer_get_seconds(); // seconds since program started... not accurate, but good for long
// runtimes with second-based timeouts

// Optional authoritative mission clock for headless experiment runs. Wall-clock
// timer APIs remain unchanged so profiling and UI deadlines keep real time. Like the
// real clock it only moves forward, across missions too.
void timer_set_simulation_clock_enabled(bool enabled);
void timer_advance_simulation_clock(fix delta);


//=================================================================
//=================================================================
Expand Down
3 changes: 3 additions & 0 deletions code/osapi/dialogs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,9 @@ namespace os
}

if (Cmdline_noninteractive) {
// as Error() does, so the message is not lost when no log is written (e.g. release builds)
fprintf(stderr, "\n%s\n", msgStream.str().c_str());
fflush(stderr);
throw LuaErrorException(msgStream.str());
return;
}
Expand Down
13 changes: 12 additions & 1 deletion code/popup/popup.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#include <cstdarg>

#include "anim/animplay.h"
#include "cmdline/cmdline.h"
#include "freespace.h"
#include "gamesequence/gamesequence.h"
#include "gamesnd/gamesnd.h"
Expand Down Expand Up @@ -1148,7 +1149,17 @@ int popup(int flags, int nchoices, ... )
vsnprintf(Popup_info.raw_text, sizeof(Popup_info.raw_text), format, args);
va_end(args);
Popup_info.raw_text[sizeof(Popup_info.raw_text)-1] = '\0';


// A headless run has nobody to answer, so log the popup and give the abort answer, unless a script answers dialogs.
if (Cmdline_headless && !scripting::hooks::OnDialogFrame->isActive()) {
mprintf(("Headless run: skipped popup \"%s\"\n", Popup_info.raw_text));
for (i = 0; i < nchoices; i++) {
vm_free(Popup_info.button_text[i]);
Popup_info.button_text[i] = nullptr;
}
return -1;
}

gamesnd_play_iface(InterfaceSounds::POPUP_APPEAR); // play sound when popup appears

io::mouse::CursorManager::get()->pushStatus();
Expand Down
5 changes: 5 additions & 0 deletions code/radar/radarsetup.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@


#include "bmpman/bmpman.h"
#include "cmdline/cmdline.h"
#include "debugconsole/console.h"
#include "freespace.h"
#include "gamesnd/gamesnd.h"
Expand Down Expand Up @@ -174,6 +175,10 @@ void radar_stuff_blip_info(object *objp, int is_bright, color **blip_color, int

void radar_plot_object( object *objp )
{
if (Cmdline_headless) {
return;
}

vec3d pos, tempv;
float awacs_level, dist, max_radar_dist;
vec3d world_pos = objp->pos;
Expand Down
Loading