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; 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), .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_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); } } } ginit.gc.draw() catch @panic("draw 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; }