#![allow(non_snake_case)]
#![allow(non_upper_case_globals)]
use failure::{Error, ResultExt};
use ffmpeg::format;
use gl;
use gl::types::*;
use glx;
use libc::*;
use ocl;
use std::cell::RefCell;
use std::cmp;
use std::ffi::{CStr, CString};
use std::mem;
use std::ptr;
use std::result;
use std::slice;
use crate::capture::{self, GameThreadEvent};
use crate::command;
use crate::cvar;
use crate::dl;
use crate::encode;
use crate::engine::{Engine, MainThreadMarker};
use crate::fps_converter::*;
use crate::sdl;
use crate::utils::MaybeUnavailable;
use crate::utils::format_error;
type Result<T> = result::Result<T, Error>;
static mut FUNCTIONS: Option<Functions> = None;
static mut POINTERS: Option<Pointers> = None;
struct Functions {
RunListenServer: unsafe extern "C" fn(*mut c_void,
*mut c_char,
*mut c_char,
*mut c_char,
*mut c_void,
*mut c_void)
-> c_int,
CL_Disconnect: unsafe extern "C" fn(),
Cmd_AddCommand: unsafe extern "C" fn(*const c_char, *mut c_void),
Cmd_Argc: unsafe extern "C" fn() -> c_int,
Cmd_Argv: unsafe extern "C" fn(c_int) -> *const c_char,
Con_Printf: unsafe extern "C" fn(*const c_char),
Con_ToggleConsole_f: unsafe extern "C" fn(),
Cvar_RegisterVariable: unsafe extern "C" fn(*mut cvar::cvar_t),
GL_SetMode: unsafe extern "C" fn(c_int,
*mut c_void,
*mut c_void,
c_int,
*const c_char,
*const c_char) -> c_int,
Host_FilterTime: unsafe extern "C" fn(c_float) -> c_int,
Key_Event: unsafe extern "C" fn(key: c_int, down: c_int),
Memory_Init: unsafe extern "C" fn(*mut c_void, c_int),
S_PaintChannels: unsafe extern "C" fn(endtime: c_int),
S_TransferStereo16: unsafe extern "C" fn(end: c_int),
Sys_VID_FlipScreen: unsafe extern "C" fn(),
VideoMode_GetCurrentVideoMode: unsafe extern "C" fn(*mut c_int, *mut c_int, *mut c_int),
VideoMode_IsWindowed: unsafe extern "C" fn() -> c_int,
}
struct Pointers {
cls: *mut client_static_t,
game: *mut *mut CGame,
host_frametime: *mut c_double,
paintbuffer: *mut portable_samplepair_t,
paintedtime: *mut c_int,
realtime: *mut c_double,
s_BackBufferFBO: *mut FBO_Container_t,
shm: *mut *mut dma_t,
window_rect: *mut RECT,
}
thread_local! {
static AUDIO_BUFFER: RefCell<Option<capture::AudioBuffer>> = RefCell::new(None);
}
pub enum SoundCaptureMode {
Normal,
Remaining,
}
pub struct OclGlTexture {
image: ocl::Image<u8>,
}
pub enum FrameCapture {
OpenGL(fn(MainThreadMarker<'_>, (u32, u32), &mut [u8])),
OpenCL(OclGlTexture),
}
impl OclGlTexture {
fn new(_: MainThreadMarker<'_>,
texture: GLuint,
queue: ocl::Queue,
dims: ocl::SpatialDims)
-> Self {
unsafe {
gl::Finish();
}
let descr = ocl::builders::ImageDescriptor::new(ocl::enums::MemObjectType::Image2d,
dims[0],
dims[1],
1,
1,
0,
0,
None);
let image =
ocl::Image::<u8>::from_gl_texture(queue,
ocl::flags::MEM_READ_ONLY,
descr,
ocl::core::GlTextureTarget::GlTexture2d,
0,
texture).expect("ocl::Image::from_gl_texture()");
image.cmd().gl_acquire().enq().expect("gl_acquire()");
Self { image }
}
}
impl AsRef<ocl::Image<u8>> for OclGlTexture {
#[inline]
fn as_ref(&self) -> &ocl::Image<u8> {
&self.image
}
}
impl Drop for OclGlTexture {
fn drop(&mut self) {
self.image.cmd().gl_release().enq().expect("gl_release()");
}
}
#[repr(C)]
#[derive(Debug, Clone, Copy)]
struct RECT {
left: c_int,
right: c_int,
top: c_int,
bottom: c_int,
}
#[repr(C)]
#[derive(Debug, Clone, Copy)]
struct FBO_Container_t {
FBO: GLuint,
CB: GLuint,
DB: GLuint,
Tex: GLuint,
}
#[repr(C)]
struct client_static_t {
stuff: [u8; 0x4060],
demoplayback: c_int,
}
#[repr(C)]
#[derive(Debug, Clone, Copy)]
struct portable_samplepair_t {
left: c_int,
right: c_int,
}
#[repr(C)]
struct dma_t {
gamealive: c_int,
soundalive: c_int,
splitbuffer: c_int,
channels: c_int,
samples: c_int,
submission_chunk: c_int,
samplepos: c_int,
samplebits: c_int,
speed: c_int,
dmaspeed: c_int,
buffer: *mut c_uchar,
}
#[repr(C)]
struct CGame {
stuff: [u8; 0xC],
m_hSDLGLContext: *mut c_void,
}
#[export_name = "_Z15RunListenServerPvPcS0_S0_PFP14IBaseInterfacePKcPiES7_"]
pub unsafe extern "C" fn RunListenServer(instance: *mut c_void,
basedir: *mut c_char,
cmdline: *mut c_char,
postRestartCmdLineArgs: *mut c_char,
launcherFactory: *mut c_void,
filesystemFactory: *mut c_void)
-> c_int {
let mut engine = Engine::new();
if let Err(e) = refresh_pointers(engine.marker().1).context("error refreshing pointers") {
panic!("{}", &format_error(&e.into()));
}
encode::initialize();
capture::initialize(engine.marker().1);
let rv = real!(RunListenServer)(instance,
basedir,
cmdline,
postRestartCmdLineArgs,
launcherFactory,
filesystemFactory);
if let Some(FPSConverters::Sampling(mut sampling_conv)) = engine.data_mut().fps_converter.take()
{
sampling_conv.backup_and_free_ocl_data(&mut engine);
engine.data_mut().fps_converter = Some(FPSConverters::Sampling(sampling_conv));
}
engine.data_mut().ocl_yuv_buffers.reset();
engine.data_mut().pro_que.reset();
reset_pointers(engine.marker().1);
rv
}
#[no_mangle]
pub unsafe extern "C" fn CL_Disconnect() {
if capture::is_capturing() && (*ptr!(cls)).demoplayback != 0 {
let mut engine = Engine::new();
if cap_playdemostop.parse(&mut engine).unwrap_or(0) != 0 {
capture::stop(&mut engine);
}
}
real!(CL_Disconnect)();
}
#[no_mangle]
pub unsafe extern "C" fn Con_ToggleConsole_f() {
let mut engine = Engine::new();
if !engine.data().inside_key_event
|| cap_allow_tabbing_out_in_demos.parse(&mut engine)
.unwrap_or(0)
== 0
{
real!(Con_ToggleConsole_f)();
}
}
#[no_mangle]
pub unsafe extern "C" fn GL_SetMode(mainwindow: c_int,
pmaindc: *mut c_void,
pbaseRC: *mut c_void,
fD3D: c_int,
pszDriver: *const c_char,
pszCmdLine: *const c_char)
-> c_int {
let mut engine = Engine::new();
engine.data_mut().inside_gl_setmode = true;
let rv = real!(GL_SetMode)(mainwindow, pmaindc, pbaseRC, fD3D, pszDriver, pszCmdLine);
engine.data_mut().inside_gl_setmode = false;
rv
}
#[no_mangle]
pub unsafe extern "C" fn Host_FilterTime(time: c_float) -> c_int {
let engine = Engine::new();
let old_realtime = *ptr!(realtime);
let rv = real!(Host_FilterTime)(time);
if capture::is_capturing() && (*ptr!(cls)).demoplayback != 0 {
let params = capture::get_capture_parameters(&engine);
let frametime = params.sampling_time_base.unwrap_or(params.time_base).into();
*ptr!(host_frametime) = frametime;
*ptr!(realtime) = old_realtime + frametime;
return 1;
}
rv
}
#[no_mangle]
pub unsafe extern "C" fn Key_Event(key: c_int, down: c_int) {
let mut engine = Engine::new();
engine.data_mut().inside_key_event = true;
real!(Key_Event)(key, down);
engine.data_mut().inside_key_event = false;
}
#[no_mangle]
pub unsafe extern "C" fn Memory_Init(buf: *mut c_void, size: c_int) {
real!(Memory_Init)(buf, size);
let mut engine = Engine::new();
register_cvars_and_commands(&mut engine);
gl::ReadPixels::load_with(|s| sdl::get_proc_address(s) as _);
if !gl::ReadPixels::is_loaded() {
panic!("could not load glReadPixels()");
}
gl::PixelStorei::load_with(|s| sdl::get_proc_address(s) as _);
if !gl::PixelStorei::is_loaded() {
panic!("could not load glPixelStorei()");
}
gl::Finish::load_with(|s| sdl::get_proc_address(s) as _);
if !gl::Finish::is_loaded() {
panic!("could not load glFinish()");
}
}
#[no_mangle]
pub unsafe extern "C" fn S_PaintChannels(endtime: c_int) {
let mut engine = Engine::new();
if !capture::is_capturing() {
engine.data_mut().capture_sound = false;
real!(S_PaintChannels)(endtime);
return;
}
if engine.data().capture_sound {
let paintedtime = *ptr!(paintedtime);
let frametime = match engine.data().sound_capture_mode {
SoundCaptureMode::Normal => *ptr!(host_frametime),
SoundCaptureMode::Remaining => capture::get_capture_parameters(&engine).sound_extra,
};
let speed = (**ptr!(shm)).speed;
let samples = frametime * f64::from(speed) + engine.data().sound_remainder;
let samples_rounded = match engine.data().sound_capture_mode {
SoundCaptureMode::Normal => samples.floor(),
SoundCaptureMode::Remaining => samples.ceil(),
};
engine.data_mut().sound_remainder = samples - samples_rounded;
AUDIO_BUFFER.with(|b| {
let mut buf = capture::get_audio_buffer(engine.marker().1);
buf.data_mut().clear();
*b.borrow_mut() = Some(buf);
});
real!(S_PaintChannels)(paintedtime + samples_rounded as i32);
AUDIO_BUFFER.with(|b| {
capture::capture_audio(engine.marker().1, b.borrow_mut().take().unwrap())
});
engine.data_mut().capture_sound = false;
}
}
#[no_mangle]
pub unsafe extern "C" fn S_TransferStereo16(end: c_int) {
let engine = Engine::new();
if engine.data().capture_sound {
AUDIO_BUFFER.with(|b| {
let mut buf = b.borrow_mut();
let buf = buf.as_mut().unwrap().data_mut();
let paintedtime = *ptr!(paintedtime);
let paintbuffer = slice::from_raw_parts_mut(ptr!(paintbuffer), 1026);
let engine = Engine::new();
let volume =
(capture::get_capture_parameters(&engine).volume * 256f32) as i32;
for sample in paintbuffer.iter().take((end - paintedtime) as usize * 2) {
let l16 = cmp::min(32767, cmp::max(-32768, (sample.left * volume) >> 8))
as i16;
let r16 = cmp::min(32767,
cmp::max(-32768, (sample.right * volume) >> 8))
as i16;
buf.push((l16, r16));
}
});
}
real!(S_TransferStereo16)(end);
}
#[export_name = "_Z18Sys_VID_FlipScreenv"]
pub unsafe extern "C" fn Sys_VID_FlipScreen() {
let mut engine = Engine::new();
while let Some(e) = capture::get_event(engine.marker().1) {
match e {
GameThreadEvent::Message(msg) => con_print(&msg),
GameThreadEvent::EncoderPixelFormat(fmt) => {
engine.data_mut().encoder_pixel_format = Some(fmt)
}
}
}
while capture::is_capturing() && engine.data().encoder_pixel_format.is_none() {
match capture::get_event_block(engine.marker().1) {
GameThreadEvent::Message(msg) => con_print(&msg),
GameThreadEvent::EncoderPixelFormat(fmt) => {
engine.data_mut().encoder_pixel_format = Some(fmt)
}
}
}
if capture::is_capturing() {
engine.data_mut().capture_sound = true;
match engine.data_mut().fps_converter.take().unwrap() {
FPSConverters::Simple(mut simple_conv) => {
simple_conv.time_passed(&mut engine, *ptr!(host_frametime), capture_frame);
engine.data_mut().fps_converter = Some(FPSConverters::Simple(simple_conv));
}
FPSConverters::Sampling(mut sampling_conv) => {
sampling_conv.time_passed(&mut engine, *ptr!(host_frametime), capture_frame);
engine.data_mut().fps_converter = Some(FPSConverters::Sampling(sampling_conv));
}
}
}
real!(Sys_VID_FlipScreen)();
}
#[no_mangle]
pub unsafe fn VideoMode_IsWindowed() -> c_int {
real!(VideoMode_IsWindowed)()
}
fn refresh_pointers(_: MainThreadMarker<'_>) -> Result<()> {
let hw = dl::open("hw.so", RTLD_NOW | RTLD_NOLOAD).context("couldn't load hw.so")?;
unsafe {
FUNCTIONS = Some(Functions { RunListenServer: find!(
hw,
"_Z15RunListenServerPvPcS0_S0_PFP14IBaseInterfacePKcPiES7_"
),
CL_Disconnect: find!(hw, "CL_Disconnect"),
Cmd_AddCommand: find!(hw, "Cmd_AddCommand"),
Cmd_Argc: find!(hw, "Cmd_Argc"),
Cmd_Argv: find!(hw, "Cmd_Argv"),
Con_Printf: find!(hw, "Con_Printf"),
Con_ToggleConsole_f: find!(hw, "Con_ToggleConsole_f"),
Cvar_RegisterVariable: find!(hw, "Cvar_RegisterVariable"),
GL_SetMode: find!(hw, "GL_SetMode"),
Host_FilterTime: find!(hw, "Host_FilterTime"),
Key_Event: find!(hw, "Key_Event"),
Memory_Init: find!(hw, "Memory_Init"),
S_PaintChannels: find!(hw, "S_PaintChannels"),
S_TransferStereo16: find!(hw, "S_TransferStereo16"),
Sys_VID_FlipScreen: find!(hw, "_Z18Sys_VID_FlipScreenv"),
VideoMode_GetCurrentVideoMode: find!(
hw,
"VideoMode_GetCurrentVideoMode"
),
VideoMode_IsWindowed: find!(hw, "VideoMode_IsWindowed"), });
POINTERS = Some(Pointers { cls: find!(hw, "cls"),
game: find!(hw, "game"),
host_frametime: find!(hw, "host_frametime"),
paintbuffer: find!(hw, "paintbuffer"),
paintedtime: find!(hw, "paintedtime"),
realtime: find!(hw, "realtime"),
s_BackBufferFBO: find!(hw, "s_BackBufferFBO"),
shm: find!(hw, "shm"),
window_rect: find!(hw, "window_rect") });
}
Ok(())
}
#[inline]
fn reset_pointers(_: MainThreadMarker<'_>) {
unsafe {
FUNCTIONS = None;
POINTERS = None;
}
}
fn register_cvars_and_commands(engine: &mut Engine) {
for cmd in &command::COMMANDS {
unsafe {
register_command(cmd.name(), cmd.callback());
}
}
for cvar in &cvar::CVARS {
if let Err(e) = cvar.register(engine)
.context("error registering a console variable")
{
panic!("{}", format_error(&e.into()));
}
}
}
#[inline]
unsafe fn register_command(name: &'static [u8], callback: unsafe extern "C" fn()) {
real!(Cmd_AddCommand)(name as *const _ as *const _, callback as *mut c_void);
}
#[inline]
pub unsafe fn register_variable(cvar: &mut cvar::cvar_t) {
real!(Cvar_RegisterVariable)(cvar);
}
#[inline]
pub unsafe fn con_print(string: &str) {
let cstring =
CString::new(string.replace('%', "%%")).expect("string cannot contain null bytes");
real!(Con_Printf)(cstring.as_ptr())
}
#[inline]
pub unsafe fn cmd_argc() -> u32 {
let argc = real!(Cmd_Argc)();
cmp::max(0, argc) as u32
}
#[inline]
pub unsafe fn cmd_argv(index: u32) -> String {
let index = cmp::min(index, i32::max_value() as u32) as i32;
let arg = real!(Cmd_Argv)(index);
CStr::from_ptr(arg).to_string_lossy().into_owned()
}
pub fn get_resolution(_: MainThreadMarker<'_>) -> (u32, u32) {
let mut width;
let mut height;
unsafe {
if real!(VideoMode_IsWindowed)() != 0 {
let window_rect = *ptr!(window_rect);
width = window_rect.right - window_rect.left;
height = window_rect.bottom - window_rect.top;
} else {
width = mem::uninitialized();
height = mem::uninitialized();
real!(VideoMode_GetCurrentVideoMode)(&mut width, &mut height, ptr::null_mut());
}
}
width = cmp::max(0, width);
height = cmp::max(0, height);
(width as u32, height as u32)
}
#[inline]
pub fn reset_sound_capture_remainder(engine: &mut Engine) {
engine.data_mut().sound_remainder = 0f64;
}
#[inline]
pub fn capture_remaining_sound(engine: &mut Engine) {
engine.data_mut().sound_capture_mode = SoundCaptureMode::Remaining;
engine.data_mut().capture_sound = true;
unsafe {
S_PaintChannels(0);
}
engine.data_mut().sound_capture_mode = SoundCaptureMode::Normal;
}
pub fn get_pro_que(engine: &mut Engine) -> Option<&mut ocl::ProQue> {
if engine.data().pro_que.is_not_checked() {
let context = ocl::Context::builder().gl_context(get_opengl_context(engine.marker().1))
.glx_display(unsafe { glx::GetCurrentDisplay() } as _)
.build()
.context("error building ocl::Context");
let pro_que = context.and_then(|ctx| {
ocl::ProQue::builder().context(ctx)
.prog_bldr({
let mut builder =
ocl::Program::builder();
builder
.src(include_str!("../../cl_src/color_conversion.cl"))
.src(include_str!("../../cl_src/sampling.cl"));
builder
})
.build()
.context("error building ocl::ProQue")
})
.map_err(|e| {
engine.con_print(&format!("Could not initialize OpenCL, \
proceeding without it. \
Error details:\n{}",
e).replace('\0', "\\x00"));
})
.ok();
engine.data_mut().pro_que = MaybeUnavailable::from_check_result(pro_que);
}
engine.data_mut().pro_que.as_mut().available()
}
fn build_ocl_buffer(pro_que: &ocl::ProQue, length: usize) -> Result<ocl::Buffer<u8>> {
Ok(ocl::Buffer::<u8>::builder().queue(pro_que.queue().clone())
.flags(ocl::flags::MemFlags::new().write_only().host_read_only())
.len(length)
.build()
.context("could not build the OpenCL buffer")?)
}
pub fn build_ocl_image<T: ocl::OclPrm>(pro_que: &ocl::ProQue,
mem_flags: ocl::MemFlags,
data_type: ocl::enums::ImageChannelDataType,
dims: ocl::SpatialDims)
-> Result<ocl::Image<T>> {
Ok(ocl::Image::<T>::builder().channel_order(ocl::enums::ImageChannelOrder::Rgba)
.channel_data_type(data_type)
.image_type(ocl::enums::MemObjectType::Image2d)
.dims(dims)
.flags(mem_flags)
.queue(pro_que.queue().clone())
.build()
.context("could not build the OpenCL image")?)
}
fn build_yuv_buffers(pro_que: &ocl::ProQue,
(Y_len, U_len, V_len): (usize, usize, usize))
-> Option<(ocl::Buffer<u8>, ocl::Buffer<u8>, ocl::Buffer<u8>)> {
let Y_buf = build_ocl_buffer(pro_que, Y_len);
let U_buf = build_ocl_buffer(pro_que, U_len);
let V_buf = build_ocl_buffer(pro_que, V_len);
if let (Ok(Y_buf), Ok(U_buf), Ok(V_buf)) = (Y_buf, U_buf, V_buf) {
Some((Y_buf, U_buf, V_buf))
} else {
None
}
}
fn get_yuv_buffers_and_pro_que(
engine: &mut Engine,
(Y_len, U_len, V_len): (usize, usize, usize))
-> Option<(&mut (ocl::Buffer<u8>, ocl::Buffer<u8>, ocl::Buffer<u8>), &ocl::ProQue)> {
if engine.data().ocl_yuv_buffers.is_not_checked() {
let buffers = build_yuv_buffers(get_pro_que(engine).unwrap(), (Y_len, U_len, V_len));
engine.data_mut().ocl_yuv_buffers = MaybeUnavailable::from_check_result(buffers);
}
match engine.data_mut().ocl_yuv_buffers.take() {
Some((Y_buf, U_buf, V_buf)) => {
if Y_buf.len() != Y_len || U_buf.len() != U_len || V_buf.len() != V_len {
drop(Y_buf);
drop(U_buf);
drop(V_buf);
let buffers =
build_yuv_buffers(get_pro_que(engine).unwrap(), (Y_len, U_len, V_len));
engine.data_mut().ocl_yuv_buffers = MaybeUnavailable::from_check_result(buffers);
} else {
engine.data_mut().ocl_yuv_buffers =
MaybeUnavailable::Available((Y_buf, U_buf, V_buf));
}
let data = engine.data_mut();
let pro_que = &data.pro_que;
data.ocl_yuv_buffers
.as_mut()
.available()
.map(|buffers| (buffers, pro_que.as_ref().unwrap()))
}
None => None,
}
}
fn capture_frame(engine: &mut Engine) -> FrameCapture {
let (engine, marker) = engine.marker_mut();
let texture = unsafe { *ptr!(s_BackBufferFBO) }.Tex;
let pro_que = if texture != 0 {
get_pro_que(engine)
} else {
None
};
if let Some(pro_que) = pro_que {
let (w, h) = get_resolution(marker);
FrameCapture::OpenCL(OclGlTexture::new(marker,
texture,
pro_que.queue().clone(),
(w, h).into()))
} else {
FrameCapture::OpenGL(read_pixels)
}
}
fn ocl_color_conversion_func_name(target: format::Pixel) -> Option<&'static str> {
match target {
format::Pixel::YUV420P => Some("rgb_to_yuv420_601_limited"),
format::Pixel::YUV444P => Some("rgb_to_yuv444_601_limited"),
_ => None,
}
}
pub fn read_ocl_image_into_buf<T: ocl::OclPrm>(engine: &mut Engine,
image: &ocl::Image<T>,
buf: &mut capture::VideoBuffer) {
let encoder_pixel_format = engine.data().encoder_pixel_format.unwrap();
if let Some(func_name) = ocl_color_conversion_func_name(encoder_pixel_format) {
buf.set_format(encoder_pixel_format);
let frame = buf.get_frame();
if let Some((&mut (ref Y_buf, ref U_buf, ref V_buf), pro_que)) =
get_yuv_buffers_and_pro_que(engine,
(frame.data(0).len(),
frame.data(1).len(),
frame.data(2).len()))
{
let kernel = pro_que.kernel_builder(func_name)
.global_work_size(image.dims())
.arg(image)
.arg(frame.stride(0))
.arg(frame.stride(1))
.arg(frame.stride(2))
.arg(Y_buf)
.arg(U_buf)
.arg(V_buf)
.build()
.unwrap();
unsafe {
kernel.enq().expect("kernel.enq()");
}
Y_buf.read(frame.data_mut(0)).enq().expect("Y_buf.read()");
U_buf.read(frame.data_mut(1)).enq().expect("U_buf.read()");
V_buf.read(frame.data_mut(2)).enq().expect("V_buf.read()");
return;
}
}
buf.set_format(format::Pixel::RGBA);
let pro_que = get_pro_que(engine).unwrap();
let ocl_buffer =
build_ocl_buffer(pro_que, buf.as_mut_slice().len()).expect("OpenCL buffer build");
let kernel = pro_que.kernel_builder("rgba_to_uint8_rgba_buffer")
.global_work_size(image.dims())
.arg(image)
.arg(&ocl_buffer)
.build()
.unwrap();
unsafe {
kernel.enq().expect("kernel.enq()");
}
ocl_buffer.read(buf.as_mut_slice())
.enq()
.expect("buffer.read()");
}
fn read_pixels(_: MainThreadMarker<'_>, (w, h): (u32, u32), buf: &mut [u8]) {
unsafe {
gl::PixelStorei(gl::PACK_ALIGNMENT, 1);
gl::ReadPixels(0,
0,
w as GLsizei,
h as GLsizei,
gl::RGB,
gl::UNSIGNED_BYTE,
buf.as_mut_ptr() as _);
}
}
fn get_opengl_context(_: MainThreadMarker<'_>) -> *mut c_void {
unsafe { (**ptr!(game)).m_hSDLGLContext }
}
cvar!(cap_allow_tabbing_out_in_demos, "1");
cvar!(cap_playdemostop, "1");