engine/src/game.zig

579 lines
22 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 AssetManager = @import("AssetManager.zig");
const Render = @import("Render.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;
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(
"Learn OpenGL with Zig!",
c.SDL_WINDOWPOS_UNDEFINED,
c.SDL_WINDOWPOS_UNDEFINED,
globals.DEFAULT_WIDTH,
globals.DEFAULT_HEIGHT,
c.SDL_WINDOW_SHOWN | c.SDL_WINDOW_OPENGL | c.SDL_WINDOW_ALLOW_HIGHDPI | c.SDL_WINDOW_RESIZABLE,
);
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 = context,
.width = globals.DEFAULT_WIDTH,
.height = globals.DEFAULT_HEIGHT,
};
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);
}
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();
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),
.assetman = AssetManager.init(global_allocator.*, globals.g_mem.frame_fba.allocator()),
.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.render.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_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(60, 15, 0)) },
.light = .{ .color_intensity = Vec4.new(1, 1, 0.83, 1) },
});
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, 100.0) },
.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, 100.0) },
.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, 10.0) },
.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 = .{
.normal_map = a.Textures.@"tile.norm",
},
.override_material = true,
},
});
// 10 dielectric 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, 0) },
.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(i)) / 10.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 = .{
.albedo = Vec3.new(1.000, 0.766, 0.336),
// .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.amd_ryzen_9.scene));
const ent = globals.g_mem.world.getEntity(scene) orelse @panic("WTF");
ent.data.transform.pos = Vec3.new(0, 1, 0);
ent.data.transform.scale = Vec3.one().scale(0.2);
}
export fn game_update() bool {
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();
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;
}
},
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
{
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);
}
}
}
// Render
{
gmem.render.begin();
defer gmem.render.finish();
// Collect point lights
{
const point_lights = gmem.render.getPointLights();
point_lights.count = 0;
for (0..gmem.world.entity_count) |i| {
const ent = &gmem.world.entities[i];
if (!ent.data.flags.active) continue;
if (ent.data.flags.point_light) {
const pos = ent.globalMatrix(&gmem.world).extractTranslation();
var pos4 = Vec4.new(pos.x(), pos.y(), pos.z(), 1.0);
const color = ent.data.light.premultipliedColor();
point_lights.lights[point_lights.count] = .{
.pos = Vec4.new(pos4.x(), pos4.y(), pos4.z(), 1),
.color_radius = Vec4.new(color.x(), color.y(), color.z(), ent.data.point_light.radius),
};
point_lights.count += 1;
if (point_lights.count == Render.MAX_POINT_LIGHTS) {
break;
}
}
if (ent.data.flags.dir_light) {
const dir4 = ent.globalMatrix(&gmem.world).mulByVec4(Vec4.forward());
const color = ent.data.light.premultipliedColor();
point_lights.lights[point_lights.count] = .{
.pos = dir4,
.color_radius = Vec4.new(color.x(), color.y(), color.z(), 1),
};
point_lights.count += 1;
if (point_lights.count == Render.MAX_POINT_LIGHTS) {
break;
}
}
}
gmem.render.flushUBOs();
// Render meshes and 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 = ent.data.light.premultipliedColor() },
.transform = ent.globalMatrix(&gmem.world).*,
});
}
}
}
}
globals.g_assetman.watchChanges();
return true;
}
export fn game_shutdown() void {
const gmem = globals.g_mem;
std.log.debug("game_shutdown\n", .{});
gmem.global_allocator.free(gmem.frame_fba.buffer);
gmem.global_allocator.destroy(gmem);
gl.disable(gl.DEBUG_OUTPUT);
}
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;
}