engine/src/game.zig

622 lines
25 KiB
Zig

const std = @import("std");
const globals = @import("globals.zig");
const InitMemory = globals.InitMemory;
const GameMemory = globals.GameMemory;
const c = @import("sdl.zig");
// const gl = @import("gl.zig");
const DescriptorManager = @import("DescriptorManager.zig");
const AssetManager = @import("AssetManager.zig");
const Render = @import("Render.zig");
const Render2 = @import("Render2.zig");
const formats = @import("formats.zig");
const za = @import("zalgebra");
const Vec2 = za.Vec2;
const Vec3 = za.Vec3;
const Vec4 = za.Vec4;
const Mat4 = za.Mat4;
const Quat = za.Quat;
const a = @import("asset_manifest");
const windows = std.os.windows;
const tracy = @import("tracy");
const GraphicsContext = @import("GraphicsContext.zig");
pub extern "dwmapi" fn DwmEnableMMCSS(fEnableMMCSS: windows.BOOL) callconv(windows.WINAPI) windows.HRESULT;
pub extern "dwmapi" fn DwmFlush() callconv(windows.WINAPI) void;
const D3DKMT_HANDLE = c_uint;
const D3DDDI_VIDEO_PRESENT_SOURCE_ID = c_uint;
const D3DKMT_WAITFORVERTICALBLANKEVENT = extern struct {
hAdapter: D3DKMT_HANDLE,
hDevice: D3DKMT_HANDLE,
VidPnSourceId: D3DDDI_VIDEO_PRESENT_SOURCE_ID,
};
pub extern "gdi32" fn D3DKMTWaitForVerticalBlankEvent(event: *const D3DKMT_WAITFORVERTICALBLANKEVENT) callconv(windows.WINAPI) windows.NTSTATUS;
const FRAME_ARENA_SIZE = 1024 * 1024 * 512;
fn game_init_window_err(global_allocator: std.mem.Allocator) !void {
if (c.SDL_SetHint(c.SDL_HINT_WINDOWS_DPI_AWARENESS_, "permonitorv2") == c.SDL_FALSE) {
std.log.debug("Failed to setup windows DPI scaling\n", .{});
}
if (c.SDL_SetHint(c.SDL_HINT_WINDOWS_DPI_SCALING_, "1") == c.SDL_FALSE) {
std.log.debug("Failed to setup windows DPI scaling\n", .{});
}
// _ = DwmEnableMMCSS(1);
try sdl_try(c.SDL_Init(c.SDL_INIT_EVERYTHING));
// try sdl_try(c.SDL_GL_SetAttribute(c.SDL_GL_DOUBLEBUFFER, 1));
// try sdl_try(c.SDL_GL_SetAttribute(c.SDL_GL_CONTEXT_MAJOR_VERSION, 4));
// try sdl_try(c.SDL_GL_SetAttribute(c.SDL_GL_CONTEXT_MINOR_VERSION, 5));
// try sdl_try(c.SDL_GL_SetAttribute(c.SDL_GL_CONTEXT_PROFILE_MASK, c.SDL_GL_CONTEXT_PROFILE_CORE));
// try sdl_try(c.SDL_GL_SetAttribute(c.SDL_GL_FRAMEBUFFER_SRGB_CAPABLE, 0));
const maybe_window = c.SDL_CreateWindow(
"Vulkan Engine",
c.SDL_WINDOWPOS_UNDEFINED,
c.SDL_WINDOWPOS_UNDEFINED,
globals.DEFAULT_WIDTH,
globals.DEFAULT_HEIGHT,
c.SDL_WINDOW_SHOWN | c.SDL_WINDOW_ALLOW_HIGHDPI | c.SDL_WINDOW_RESIZABLE | c.SDL_WINDOW_VULKAN,
);
if (maybe_window == null) {
std.log.err("SDL Error: {s}", .{c.SDL_GetError()});
return error.SDLWindowError;
}
const window = maybe_window.?;
// const context = c.SDL_GL_CreateContext(window);
// try sdl_try(c.SDL_GL_SetSwapInterval(0));
globals.g_init = try global_allocator.create(InitMemory);
globals.g_init_exists = true;
globals.g_init.* = .{
.global_allocator = global_allocator,
.window = window,
.context = null,
.width = globals.DEFAULT_WIDTH,
.height = globals.DEFAULT_HEIGHT,
};
try globals.g_init.gc.init(global_allocator, window);
const version = &globals.g_init.syswm_info.version;
version.major = c.SDL_MAJOR_VERSION;
version.minor = c.SDL_MINOR_VERSION;
version.patch = c.SDL_PATCHLEVEL;
// c.SDL_GL_GetDrawableSize(window, &globals.g_init.width, &globals.g_init.height);
if (c.SDL_GetWindowWMInfo(window, &globals.g_init.syswm_info) == 0) {
const err = c.SDL_GetError();
std.log.err("Failed to get syswm info: {s}", .{err});
return error.SDLSysWMInfo;
}
}
export fn game_init_window(global_allocator: *std.mem.Allocator) void {
std.log.debug("game_init_window\n", .{});
game_init_window_err(global_allocator.*) catch |err| {
std.log.err("Failed to init window {}\n", .{err});
@panic("Failed to init window");
};
}
// fn loadGL() void {
// const getProcAddress = struct {
// fn getProcAddress(ctx: @TypeOf(null), proc: [:0]const u8) ?gl.FunctionPointer {
// _ = ctx;
// return @ptrCast(c.SDL_GL_GetProcAddress(proc));
// }
// }.getProcAddress;
// gl.load(null, getProcAddress) catch |err| {
// std.log.debug("Failed to load gl funcs {}\n", .{err});
// @panic("gl.load");
// };
// gl.GL_ARB_bindless_texture.load(null, getProcAddress) catch |err| {
// std.log.debug("Failed to load gl funcs GL_ARB_bindless_texture {}\n", .{err});
// @panic("gl.load");
// };
// gl.debugMessageCallback(glDebugCallback, null);
// // gl.enable(gl.DEBUG_OUTPUT);
// // gl.enable(gl.DEBUG_OUTPUT_SYNCHRONOUS);
// }
//
// fn glDebugCallback(source: gl.GLenum, _type: gl.GLenum, id: gl.GLuint, severity: gl.GLenum, length: gl.GLsizei, message: [*:0]const u8, userParam: ?*anyopaque) callconv(.C) void {
// _ = userParam; // autofix
// const source_str = switch (source) {
// gl.DEBUG_SOURCE_API => "API",
// gl.DEBUG_SOURCE_WINDOW_SYSTEM => "WindowSystem",
// gl.DEBUG_SOURCE_APPLICATION => "App",
// gl.DEBUG_SOURCE_SHADER_COMPILER => "ShaderCompiler",
// gl.DEBUG_SOURCE_THIRD_PARTY => "ThirdParty",
// gl.DEBUG_SOURCE_OTHER => "Other",
// else => unreachable,
// };
// const type_str = switch (_type) {
// gl.DEBUG_TYPE_ERROR => "Error",
// gl.DEBUG_TYPE_DEPRECATED_BEHAVIOR => "Deprecated Behaviour",
// gl.DEBUG_TYPE_UNDEFINED_BEHAVIOR => "Undefined Behaviour",
// gl.DEBUG_TYPE_PORTABILITY => "Portability",
// gl.DEBUG_TYPE_PERFORMANCE => "Performance",
// gl.DEBUG_TYPE_MARKER => "Marker",
// gl.DEBUG_TYPE_PUSH_GROUP => "Push Group",
// gl.DEBUG_TYPE_POP_GROUP => "Pop Group",
// gl.DEBUG_TYPE_OTHER => "Other",
// else => unreachable,
// };
// switch (severity) {
// gl.DEBUG_SEVERITY_HIGH => {
// std.log.scoped(.OpenGL).err("{s}:{}:{s}: {s}", .{ source_str, id, type_str, message[0..@intCast(length)] });
// },
// gl.DEBUG_SEVERITY_MEDIUM => {
// std.log.scoped(.OpenGL).warn("{s}:{}:{s}: {s}", .{ source_str, id, type_str, message[0..@intCast(length)] });
// },
// gl.DEBUG_SEVERITY_LOW => {
// std.log.scoped(.OpenGL).debug("{s}:{}:{s}: {s}", .{ source_str, id, type_str, message[0..@intCast(length)] });
// },
// gl.DEBUG_SEVERITY_NOTIFICATION => {
// std.log.scoped(.OpenGL).info("{s}:{}:{s}: {s}", .{ source_str, id, type_str, message[0..@intCast(length)] });
// },
// else => unreachable,
// }
// }
const mesh_program = a.ShaderPrograms.mesh;
export fn game_init(global_allocator: *std.mem.Allocator) void {
// loadGL();
tracy.startupProfiler();
std.log.debug("game_init\n", .{});
globals.g_mem = global_allocator.create(GameMemory) catch @panic("OOM");
const frame_arena_buffer = global_allocator.alloc(u8, FRAME_ARENA_SIZE) catch @panic("OOM");
globals.g_mem.* = .{
.global_allocator = global_allocator.*,
.frame_fba = std.heap.FixedBufferAllocator.init(frame_arena_buffer),
.descriptorman = DescriptorManager.init(&globals.g_init.gc) catch @panic("DescriptorManager.init"),
.assetman = AssetManager.init(global_allocator.*, globals.g_mem.frame_fba.allocator(), &globals.g_init.gc, &globals.g_mem.descriptorman),
// .render = Render.init(global_allocator.*, globals.g_mem.frame_fba.allocator(), &globals.g_mem.assetman),
.world = .{ .frame_arena = globals.g_mem.frame_fba.allocator() },
};
globals.g_mem.render2.init(globals.g_mem.frame_fba.allocator(), &globals.g_init.gc, &globals.g_mem.descriptorman, &globals.g_mem.assetman) catch @panic("OOM");
globals.g_mem.render2.camera = &globals.g_mem.free_cam.camera;
std.log.debug("actual ptr: {}, correct ptr {}", .{ globals.g_mem.assetman.frame_arena.ptr, globals.g_mem.frame_fba.allocator().ptr });
globals.g_assetman = &globals.g_mem.assetman;
globals.g_assetman.initWatch();
globals.g_mem.performance_frequency = c.SDL_GetPerformanceFrequency();
globals.g_mem.last_frame_time = c.SDL_GetPerformanceCounter();
// var majorVer: gl.GLint = 0;
// var minorVer: gl.GLint = 0;
// gl.getIntegerv(gl.MAJOR_VERSION, &majorVer);
// gl.getIntegerv(gl.MINOR_VERSION, &minorVer);
// std.log.debug("OpenGL Version: {}.{}", .{ majorVer, minorVer });
// gl.viewport(0, 0, globals.g_init.width, globals.g_init.height);
_ = globals.g_mem.world.addEntity(.{
.flags = .{ .dir_light = true, .rotate = true },
.transform = .{ .rot = Quat.fromEulerAngles(Vec3.new(70, 0, 0)) },
.light = .{ .color_intensity = Vec4.new(2.00, 0.805, 0.70, 30) },
.rotate = .{ .axis = Vec3.up(), .rate = -10 },
});
// const light_root = globals.g_mem.world.addEntity(.{
// .flags = .{ .rotate = true },
// .transform = .{ .pos = Vec3.new(0, 0.1, 0) },
// .rotate = .{ .axis = Vec3.up(), .rate = 60 },
// });
// const light1 = globals.g_mem.world.addEntity(.{
// .transform = .{ .pos = Vec3.new(1.8, 1, 0) },
// .flags = .{ .point_light = true, .rotate = true },
// .light = .{ .color_intensity = Vec4.new(1.0, 0.3, 0.1, 800.0 * 2) },
// .point_light = .{ .radius = 0.1 },
// .rotate = .{ .axis = Vec3.up(), .rate = -40 },
// });
// light1.ptr.setParent(light_root.handle);
// const light2 = globals.g_mem.world.addEntity(.{
// .transform = .{ .pos = Vec3.new(-2, 0, 0) },
// .flags = .{ .point_light = true, .rotate = true },
// .light = .{ .color_intensity = Vec4.new(0.2, 0.5, 1.0, 800.0 * 2) },
// .point_light = .{ .radius = 0.1 },
// });
// light2.ptr.setParent(light1.handle);
// _ = globals.g_mem.world.addEntity(.{
// .transform = .{ .pos = Vec3.new(1, 0.5, 4) },
// .flags = .{ .point_light = true },
// .light = .{ .color_intensity = Vec4.new(0.2, 0.5, 1.0, 800.0 * 2) },
// .point_light = .{ .radius = 1 },
// });
// Plane
// _ = globals.g_mem.world.addEntity(.{
// .flags = .{ .mesh = true },
// .transform = .{ .scale = Vec3.one().scale(10) },
// .mesh = .{
// .handle = a.Meshes.plane.Plane,
// .material = .{
// .albedo = Vec4.one(),
// .normal_map = a.Textures.@"tile.norm",
// },
// .override_material = true,
// },
// });
// 10 dielectric bunnies
// {
// for (0..100) |y| {
// for (0..1) |x| {
// _ = globals.g_mem.world.addEntity(.{
// .transform = .{ .pos = Vec3.new(@as(f32, @floatFromInt(x)) * 0.3 - 0.3 * 4.5, 0, @as(f32, @floatFromInt(y)) * 0.3 - 0.3 * 4.5) },
// .flags = .{ .mesh = true },
// .mesh = .{
// .handle = a.Meshes.bunny.BunnyStanfordUVUnwrapped_res1_bun_zipper_res1,
// .material = .{
// .albedo_map = a.Textures.bunny_tex1,
// // .normal_map = a.Textures.@"tile.norm",
// .roughness = @as(f32, @floatFromInt(y)) / 100.0,
// },
// .override_material = true,
// },
// });
// }
// }
// }
// // 10 metallic bunnies
// {
// for (0..10) |i| {
// _ = globals.g_mem.world.addEntity(.{
// .transform = .{ .pos = Vec3.new(@as(f32, @floatFromInt(i)) * 0.3 - 0.3 * 4.5, 0.3, 0) },
// .flags = .{ .mesh = true },
// .mesh = .{
// .handle = a.Meshes.bunny.BunnyStanfordUVUnwrapped_res1_bun_zipper_res1,
// .material = .{
// .blend_mode = .AlphaBlend,
// .albedo = Vec4.new(1.000, 0.766, 0.336, 0.5),
// // .albedo_map = a.Textures.bunny_tex1,
// // .normal_map = a.Textures.@"tile.norm",
// .roughness = @as(f32, @floatFromInt(i + 1)) / 10.0,
// .metallic = 1.0,
// },
// .override_material = true,
// },
// });
// }
// }
// const scene = globals.g_mem.world.createScene(globals.g_assetman.resolveScene(a.Scenes.bistro.scene));
// const ent = globals.g_mem.world.getEntity(scene) orelse @panic("WTF");
// ent.data.transform.pos = Vec3.new(0, 0, 0);
// ent.data.transform.scale = Vec3.one().scale(1.0);
}
export fn game_update() bool {
const zoneGameUpdate = tracy.initZone(@src(), .{});
defer zoneGameUpdate.deinit();
const ginit = globals.g_init;
const gmem = globals.g_mem;
// std.debug.print("FPS: {d}\n", .{1.0 / g_mem.delta_time});
gmem.frame_fba.reset();
var event: c.SDL_Event = undefined;
var move = Vec3.zero();
var look = Vec2.zero();
{
const zone = tracy.initZone(@src(), .{ .name = "SDL poll events" });
defer zone.deinit();
while (c.SDL_PollEvent(&event) != 0) {
switch (event.type) {
c.SDL_QUIT => {
return false;
},
c.SDL_MOUSEMOTION => {
if (gmem.mouse_focus) {
look.xMut().* += @floatFromInt(event.motion.xrel);
look.yMut().* += @floatFromInt(event.motion.yrel);
}
},
c.SDL_MOUSEBUTTONUP => {
if (!gmem.mouse_focus) {
_ = c.SDL_SetRelativeMouseMode(c.SDL_TRUE);
gmem.mouse_focus = true;
}
},
c.SDL_MOUSEWHEEL => {
if (gmem.mouse_focus) {
gmem.free_cam.move_speed = @max(gmem.free_cam.move_speed + event.wheel.preciseY * 0.1, 0);
}
},
c.SDL_KEYUP, c.SDL_KEYDOWN => {
const pressed = event.key.state == c.SDL_PRESSED;
switch (event.key.keysym.scancode) {
// Toggle fullscreen
c.SDL_SCANCODE_RETURN => {
if (event.type == c.SDL_KEYDOWN and event.key.keysym.mod & c.KMOD_ALT > 0) {
toggleFullScreen() catch continue;
}
},
// Toggle vsync
c.SDL_SCANCODE_F10 => {
if (event.type == c.SDL_KEYDOWN) {
const newSwap: c_int = if (ginit.vsync) 0 else 1;
sdl_try(c.SDL_GL_SetSwapInterval(newSwap)) catch continue;
ginit.vsync = !ginit.vsync;
}
},
// Freeze view frustum
c.SDL_SCANCODE_F8 => {
if (event.type == c.SDL_KEYDOWN) {
gmem.render.update_view_frustum = !gmem.render.update_view_frustum;
}
},
// Expand camera far
c.SDL_SCANCODE_F7 => {
if (event.type == c.SDL_KEYDOWN) {
if (gmem.render.camera.far == 10) {
gmem.render.camera.far = 1000;
} else {
gmem.render.camera.far = 10;
}
}
},
c.SDL_SCANCODE_ESCAPE => {
if (event.type == c.SDL_KEYUP) {
if (ginit.fullscreen) {
toggleFullScreen() catch continue;
} else if (gmem.mouse_focus) {
_ = c.SDL_SetRelativeMouseMode(c.SDL_FALSE);
gmem.mouse_focus = false;
} else {
return false;
}
}
},
c.SDL_SCANCODE_W => {
gmem.input_state.forward = pressed;
},
c.SDL_SCANCODE_S => {
gmem.input_state.backward = pressed;
},
c.SDL_SCANCODE_A => {
gmem.input_state.left = pressed;
},
c.SDL_SCANCODE_D => {
gmem.input_state.right = pressed;
},
c.SDL_SCANCODE_SPACE => {
gmem.input_state.up = pressed;
},
c.SDL_SCANCODE_LCTRL => {
gmem.input_state.down = pressed;
},
else => {},
}
},
c.SDL_WINDOWEVENT => {
switch (event.window.event) {
c.SDL_WINDOWEVENT_SIZE_CHANGED => {
c.SDL_GL_GetDrawableSize(ginit.window, &ginit.width, &ginit.height);
std.log.debug("w: {}, h: {}\n", .{ ginit.width, ginit.height });
// gl.viewport(0, 0, ginit.width, ginit.height);
},
else => {},
}
},
else => {},
}
}
}
const now = c.SDL_GetPerformanceCounter();
gmem.delta_time = @as(f32, @floatFromInt((now - gmem.last_frame_time))) / @as(f32, @floatFromInt(gmem.performance_frequency));
gmem.last_frame_time = now;
if (gmem.input_state.forward) {
//const y = &move.data[1];
move.yMut().* += 1;
}
if (gmem.input_state.backward) {
move.yMut().* -= 1;
}
if (gmem.input_state.left) {
move.xMut().* -= 1;
}
if (gmem.input_state.right) {
move.xMut().* += 1;
}
if (gmem.input_state.up) {
move.zMut().* += 1;
}
if (gmem.input_state.down) {
move.zMut().* -= 1;
}
const f_width: f32 = @floatFromInt(ginit.width);
const f_height: f32 = @floatFromInt(ginit.height);
gmem.free_cam.camera.aspect = f_width / f_height;
gmem.rotation += 60 * gmem.delta_time;
// TODO: make this an entity
gmem.free_cam.update(gmem.delta_time, move, look.scale(0.008));
// Update
{
const zone = tracy.initZone(@src(), .{ .name = "update entities" });
defer zone.deinit();
for (gmem.world.entities[0..gmem.world.entity_count]) |*ent| {
if (!ent.data.flags.active) continue;
if (ent.data.flags.rotate) {
// ent.data.transform.rotate(ent.data.rotate.axis, ent.data.rotate.rate * gmem.delta_time);
}
}
}
gmem.render2.draw() catch @panic("RENDER ERROR");
// Render
if (false) {
const zone = tracy.initZone(@src(), .{ .name = "game.render()" });
defer zone.deinit();
gmem.render.begin();
defer gmem.render.finish();
// Collect point lights
{
for (0..gmem.world.entity_count) |i| {
const ent = &gmem.world.entities[i];
if (!ent.data.flags.active) continue;
if (ent.data.flags.mesh) {
const mat_override: ?formats.Material = if (ent.data.mesh.override_material) ent.data.mesh.material else null;
gmem.render.draw(.{
.mesh = ent.data.mesh.handle,
.material_override = mat_override,
.transform = ent.globalMatrix(&gmem.world).*,
});
} else if (ent.data.flags.point_light) {
gmem.render.draw(.{
.mesh = a.Meshes.sphere.Icosphere,
.material_override = .{ .albedo = Vec4.new(0, 0, 0, 1), .emission = ent.data.light.premultipliedColor() },
.transform = ent.globalMatrix(&gmem.world).*,
});
}
if (ent.data.flags.point_light) {
const pos = ent.globalMatrix(&gmem.world).extractTranslation();
const color = ent.data.light.premultipliedColor();
gmem.render.drawLight(.{
.point = .{
.pos = pos,
.radius = ent.data.point_light.radius,
.color = color,
},
});
}
if (ent.data.flags.dir_light) {
const dir4 = ent.globalMatrix(&gmem.world).mulByVec4(Vec4.forward());
const color = ent.data.light.premultipliedColor();
gmem.render.drawLight(.{
.directional = .{
.dir = dir4.toVec3(),
.color = color,
},
});
}
}
// TODO: get rid of this
gmem.render.flushUBOs();
}
}
// {
// const zone = tracy.initZone(@src(), .{ .name = "SDL_GL_SwapWindow" });
// defer zone.deinit();
// c.SDL_GL_SwapWindow(ginit.window);
// }
tracy.frameMark();
//c.SDL_Delay(1);
// globals.g_assetman.watchChanges();
return true;
}
export fn game_shutdown() void {
const gmem = globals.g_mem;
std.log.debug("game_shutdown\n", .{});
gmem.assetman.deinit();
gmem.global_allocator.free(gmem.frame_fba.buffer);
gmem.global_allocator.destroy(gmem);
// gl.disable(gl.DEBUG_OUTPUT);
tracy.shutdownProfiler();
}
export fn game_shutdown_window() void {
std.log.debug("game_shutdown_window\n", .{});
c.SDL_GL_DeleteContext(globals.g_init.context);
c.SDL_DestroyWindow(globals.g_init.window);
globals.g_init.global_allocator.destroy(globals.g_init);
globals.g_init_exists = false;
c.SDL_Quit();
}
export fn game_hot_reload(init_memory: ?*anyopaque, gmemory: ?*anyopaque) void {
std.log.debug("game_hot_reload {any} {any}\n", .{ init_memory, gmemory });
if (init_memory) |init_mem| {
globals.g_init = @alignCast(@ptrCast(init_mem));
globals.g_init_exists = true;
// loadGL();
}
if (gmemory) |gmem| {
globals.g_mem = @alignCast(@ptrCast(gmem));
globals.g_assetman = &globals.g_mem.assetman;
}
if (globals.g_init_exists) {
c.SDL_RaiseWindow(globals.g_init.window);
}
}
export fn game_memory() *anyopaque {
return @ptrCast(globals.g_mem);
}
export fn game_init_memory() *anyopaque {
return @ptrCast(globals.g_init);
}
export fn game_memory_size() usize {
return @sizeOf(GameMemory);
}
export fn game_init_memory_size() usize {
return @sizeOf(InitMemory);
}
fn sdl_try(result: c_int) !void {
if (result < 0) {
std.log.err("SDL Error: {s}", .{c.SDL_GetError()});
return error.SDLError;
}
}
fn toggleFullScreen() !void {
const ginit = globals.g_init;
const current_flags: c.Uint32 = if (globals.g_init.fullscreen) c.SDL_WINDOW_FULLSCREEN else 0;
const new_flags: c.Uint32 = if (globals.g_init.fullscreen) 0 else c.SDL_WINDOW_FULLSCREEN;
const display_index = c.SDL_GetWindowDisplayIndex(ginit.window);
var mode: c.SDL_DisplayMode = undefined;
try sdl_try(c.SDL_GetDesktopDisplayMode(display_index, &mode));
// When going fullscreen set a good display mode
if (!ginit.fullscreen) {
try sdl_try(c.SDL_SetWindowDisplayMode(ginit.window, &mode));
}
try sdl_try(c.SDL_SetWindowFullscreen(ginit.window, new_flags));
errdefer {
sdl_try(c.SDL_SetWindowFullscreen(ginit.window, current_flags)) catch {
std.log.err("Failed to change fullscreen mode and then failed to restore the old fullscreen mode\n", .{});
};
}
ginit.fullscreen = !ginit.fullscreen;
}