allocator: std.mem.Allocator, doomSampler: *gpu.GPUTexture = undefined, gpuTransferBuffer: *gpu.GPUTransferBuffer = undefined, texture: *gpu.GPUTexture = undefined, width: u32, height: u32, inputEnabled: bool = false, sensitivity: f32 = 1.0, fn log(comptime fmt: []const u8, args: anytype) void { core.engine_log("[Doomplayer] " ++ fmt, args); } pub fn create(allocator: std.mem.Allocator) !*@This() { const self = try allocator.create(@This()); self.* = .{ .allocator = allocator, .width = 320, .height = 200, }; log("initialized", .{}); try self.setup(); self.sensitivity = core.configVar(f32, "doomplayer.sensitivity", 1.0); gDoom = self; return self; } pub fn setup(self: *@This()) !void { try self.createTextures(); try backlog.platform.context().addSDLProcessFunction(processFunc); c.doom_set_resolution(320, 200); c.doom_set_default_int("key_up", c.DOOM_KEY_W); c.doom_set_default_int("key_down", c.DOOM_KEY_S); c.doom_set_default_int("key_strafeleft", c.DOOM_KEY_A); c.doom_set_default_int("key_straferight", c.DOOM_KEY_D); c.doom_set_default_int("key_use", c.DOOM_KEY_E); c.doom_set_default_int("mouse_move", 0); // Mouse will not move forward c.doom_init(1, @constCast(@ptrCast(@alignCast(&.{"doom"}))), 8); } fn sdlToDoomButton(button: c_uint) c_uint { switch (button) { sdl3_c.SDL_BUTTON_LEFT => { return c.DOOM_LEFT_BUTTON; }, sdl3_c.SDL_BUTTON_RIGHT => { return c.DOOM_RIGHT_BUTTON; }, sdl3_c.SDL_BUTTON_MIDDLE => { return c.DOOM_MIDDLE_BUTTON; }, else => {}, } return 0; } fn sdlToDoomScancode(sdlScancode: c_uint) c_int { switch (sdlScancode) { sdl3_c.SDL_SCANCODE_TAB => { return c.DOOM_KEY_TAB; }, sdl3_c.SDL_SCANCODE_RETURN => { return c.DOOM_KEY_ENTER; }, sdl3_c.SDL_SCANCODE_ESCAPE => { return c.DOOM_KEY_ESCAPE; }, sdl3_c.SDL_SCANCODE_SPACE => { return c.DOOM_KEY_SPACE; }, sdl3_c.SDL_SCANCODE_APOSTROPHE => { return c.DOOM_KEY_APOSTROPHE; }, sdl3_c.SDL_SCANCODE_KP_MULTIPLY => { return c.DOOM_KEY_MULTIPLY; }, sdl3_c.SDL_SCANCODE_COMMA => { return c.DOOM_KEY_COMMA; }, sdl3_c.SDL_SCANCODE_MINUS => { return c.DOOM_KEY_MINUS; }, sdl3_c.SDL_SCANCODE_PERIOD => { return c.DOOM_KEY_PERIOD; }, sdl3_c.SDL_SCANCODE_SLASH => { return c.DOOM_KEY_SLASH; }, sdl3_c.SDL_SCANCODE_0 => { return c.DOOM_KEY_0; }, sdl3_c.SDL_SCANCODE_1 => { return c.DOOM_KEY_1; }, sdl3_c.SDL_SCANCODE_2 => { return c.DOOM_KEY_2; }, sdl3_c.SDL_SCANCODE_3 => { return c.DOOM_KEY_3; }, sdl3_c.SDL_SCANCODE_4 => { return c.DOOM_KEY_4; }, sdl3_c.SDL_SCANCODE_5 => { return c.DOOM_KEY_5; }, sdl3_c.SDL_SCANCODE_6 => { return c.DOOM_KEY_6; }, sdl3_c.SDL_SCANCODE_7 => { return c.DOOM_KEY_7; }, sdl3_c.SDL_SCANCODE_8 => { return c.DOOM_KEY_8; }, sdl3_c.SDL_SCANCODE_9 => { return c.DOOM_KEY_9; }, sdl3_c.SDL_SCANCODE_SEMICOLON => { return c.DOOM_KEY_SEMICOLON; }, sdl3_c.SDL_SCANCODE_EQUALS => { return c.DOOM_KEY_EQUALS; }, sdl3_c.SDL_SCANCODE_LEFTBRACKET => { return c.DOOM_KEY_LEFT_BRACKET; }, sdl3_c.SDL_SCANCODE_RIGHTBRACKET => { return c.DOOM_KEY_RIGHT_BRACKET; }, sdl3_c.SDL_SCANCODE_A => { return c.DOOM_KEY_A; }, sdl3_c.SDL_SCANCODE_B => { return c.DOOM_KEY_B; }, sdl3_c.SDL_SCANCODE_C => { return c.DOOM_KEY_C; }, sdl3_c.SDL_SCANCODE_D => { return c.DOOM_KEY_D; }, sdl3_c.SDL_SCANCODE_E => { return c.DOOM_KEY_E; }, sdl3_c.SDL_SCANCODE_F => { return c.DOOM_KEY_F; }, sdl3_c.SDL_SCANCODE_G => { return c.DOOM_KEY_G; }, sdl3_c.SDL_SCANCODE_H => { return c.DOOM_KEY_H; }, sdl3_c.SDL_SCANCODE_I => { return c.DOOM_KEY_I; }, sdl3_c.SDL_SCANCODE_J => { return c.DOOM_KEY_J; }, sdl3_c.SDL_SCANCODE_K => { return c.DOOM_KEY_K; }, sdl3_c.SDL_SCANCODE_L => { return c.DOOM_KEY_L; }, sdl3_c.SDL_SCANCODE_M => { return c.DOOM_KEY_M; }, sdl3_c.SDL_SCANCODE_N => { return c.DOOM_KEY_N; }, sdl3_c.SDL_SCANCODE_O => { return c.DOOM_KEY_O; }, sdl3_c.SDL_SCANCODE_P => { return c.DOOM_KEY_P; }, sdl3_c.SDL_SCANCODE_Q => { return c.DOOM_KEY_Q; }, sdl3_c.SDL_SCANCODE_R => { return c.DOOM_KEY_R; }, sdl3_c.SDL_SCANCODE_S => { return c.DOOM_KEY_S; }, sdl3_c.SDL_SCANCODE_T => { return c.DOOM_KEY_T; }, sdl3_c.SDL_SCANCODE_U => { return c.DOOM_KEY_U; }, sdl3_c.SDL_SCANCODE_V => { return c.DOOM_KEY_V; }, sdl3_c.SDL_SCANCODE_W => { return c.DOOM_KEY_W; }, sdl3_c.SDL_SCANCODE_X => { return c.DOOM_KEY_X; }, sdl3_c.SDL_SCANCODE_Y => { return c.DOOM_KEY_Y; }, sdl3_c.SDL_SCANCODE_Z => { return c.DOOM_KEY_Z; }, sdl3_c.SDL_SCANCODE_BACKSPACE => { return c.DOOM_KEY_BACKSPACE; }, sdl3_c.SDL_SCANCODE_LCTRL => {}, sdl3_c.SDL_SCANCODE_RCTRL => { return c.DOOM_KEY_CTRL; }, sdl3_c.SDL_SCANCODE_LEFT => { return c.DOOM_KEY_LEFT_ARROW; }, sdl3_c.SDL_SCANCODE_UP => { return c.DOOM_KEY_UP_ARROW; }, sdl3_c.SDL_SCANCODE_RIGHT => { return c.DOOM_KEY_RIGHT_ARROW; }, sdl3_c.SDL_SCANCODE_DOWN => { return c.DOOM_KEY_DOWN_ARROW; }, sdl3_c.SDL_SCANCODE_LSHIFT, sdl3_c.SDL_SCANCODE_RSHIFT => { return c.DOOM_KEY_SHIFT; }, sdl3_c.SDL_SCANCODE_LALT => {}, sdl3_c.SDL_SCANCODE_RALT => { return c.DOOM_KEY_ALT; }, sdl3_c.SDL_SCANCODE_F1 => { return c.DOOM_KEY_F1; }, sdl3_c.SDL_SCANCODE_F2 => { return c.DOOM_KEY_F2; }, sdl3_c.SDL_SCANCODE_F3 => { return c.DOOM_KEY_F3; }, sdl3_c.SDL_SCANCODE_F4 => { return c.DOOM_KEY_F4; }, sdl3_c.SDL_SCANCODE_F5 => { return c.DOOM_KEY_F5; }, sdl3_c.SDL_SCANCODE_F6 => { return c.DOOM_KEY_F6; }, sdl3_c.SDL_SCANCODE_F7 => { return c.DOOM_KEY_F7; }, sdl3_c.SDL_SCANCODE_F8 => { return c.DOOM_KEY_F8; }, sdl3_c.SDL_SCANCODE_F9 => { return c.DOOM_KEY_F9; }, sdl3_c.SDL_SCANCODE_F10 => { return c.DOOM_KEY_F10; }, sdl3_c.SDL_SCANCODE_F11 => { return c.DOOM_KEY_F11; }, sdl3_c.SDL_SCANCODE_F12 => { return c.DOOM_KEY_F12; }, sdl3_c.SDL_SCANCODE_PAUSE => { return c.DOOM_KEY_PAUSE; }, else => { return c.DOOM_KEY_UNKNOWN; }, } return c.DOOM_KEY_UNKNOWN; } var gDoom: *@This() = undefined; pub fn processFunc(e: *sdl3.Event) void { if (!gDoom.inputEnabled) { return; } switch (e.type) { else => {}, sdl3_c.SDL_EVENT_MOUSE_MOTION => { c.doom_mouse_move(@intFromFloat(e.motion.xrel * 4 * gDoom.sensitivity), @intFromFloat(e.motion.yrel * 4 * gDoom.sensitivity)); }, sdl3_c.SDL_EVENT_MOUSE_BUTTON_DOWN => { c.doom_button_down(sdlToDoomButton(e.button.button)); }, sdl3_c.SDL_EVENT_MOUSE_BUTTON_UP => { c.doom_button_up(sdlToDoomButton(e.button.button)); }, sdl3_c.SDL_EVENT_KEY_DOWN => { if (!e.key.repeat) { c.doom_key_down(sdlToDoomScancode(e.key.scancode)); } }, sdl3_c.SDL_EVENT_KEY_UP => { if (!e.key.repeat) { c.doom_key_up(sdlToDoomScancode(e.key.scancode)); } }, } } fn getFrameLength(self: @This()) c_int { return self.width * self.height; } fn acquireVideoFrameBuffer(self: *@This()) []u8 { var slice: []u8 = undefined; slice.ptr = rend.context().device.mapGPUTransferBuffer(self.gpuTransferBuffer, false); slice.len = @intCast(self.width * self.width * 4); return slice; } fn releaseAndUploadFrameBuffer(self: *@This(), cmd: *gpu.GPUCommandBuffer) void { const copyPass = cmd.beginGPUCopyPass(); copyPass.uploadToGPUTexture( &.{ .transfer_buffer = self.gpuTransferBuffer, .offset = 0, .pixels_per_row = self.width, .rows_per_layer = self.height, }, &std.mem.zeroInit(gpu.GPUTextureRegion, .{ .texture = self.texture, .w = self.width, .h = self.height, .d = 1, }), true, ); copyPass.endGPUCopyPass(); } fn createTextureInner(width: u32, height: u32) !*gpu.GPUTexture { const ctx = rend.context(); const gpuTexture = ctx.device.createGPUTexture(&std.mem.zeroInit(gpu.GPUTextureCreateInfo, .{ .type = .texturetype2d, .format = .textureformatR8g8b8a8Unorm, .usage = .{ .textureusageSampler = true }, .width = @as(u32, @intCast(width)), .height = @as(u32, @intCast(height)), .layer_count_or_depth = 1, .num_levels = 1, })); return gpuTexture; } pub fn createTextures(self: *@This()) !void { self.texture = try createTextureInner(self.width, self.height); // create transfer Buffer self.gpuTransferBuffer = rend.context().device.createGPUTransferBuffer(&.{ .usage = .transferbufferusageUpload, .size = @as(u32, @intCast(self.width * self.height * 4)), .props = 0, }); } pub fn tick(self: *@This(), dt: f64) void { _ = dt; c.doom_update(); const frame = self.acquireVideoFrameBuffer(); const doomFb = c.doom_get_framebuffer(4); var i: usize = 0; while (i < self.width * self.height * 4) : (i += 1) { frame[i] = doomFb[i]; } rend.context().device.unmapGPUTransferBuffer(self.gpuTransferBuffer); const cmd = rend.context().device.acquireGPUCommandBuffer(); self.releaseAndUploadFrameBuffer(cmd); _ = cmd.submitGPUCommandBuffer(); if (self.inputEnabled) { if (ig.begin("doom settings", null, .{})) { _ = ig.sliderFloat("sensitivity", &self.sensitivity, 0.1, 20, null, .{}); ig.end(); } } } fn bindVideoTextures(p: ?*anyopaque) void { const self: *@This() = @ptrCast(@alignCast(p)); const ctx = rend.context(); const renderpass: *gpu.GPURenderPass = ctx.state.pass.?; renderpass.bindGPUFragmentSamplers(0, &.{ .sampler = ctx.blockySampler, .texture = self.texture }, 1); } // creates a video player entity pub fn createEntity(self: *@This()) !core.Entity { const entity = try core.createEntity(); const scene = entity.addComponent(core.Scene).?; const size = 5.0; const h: f32 = @floatFromInt(self.height); const w: f32 = @floatFromInt(self.width); scene.setScale(size, size, size * (h / w)); scene.setRotation(core.Rotation.eulerX(core.radians(90.0))); scene.setPosition(.{ .z = 30 }); if (entity.addComponent(rend.MeshComponent)) |mesh| { mesh.cmrf = bindVideoTextures; mesh.cmrf_ctx = self; mesh.textureMode.fullbright = true; mesh.textureMode.noSrgb = true; mesh.setMesh("m_plane"); } return entity; } pub fn destroy(self: *@This()) void { core.engine_logs("[Videoplayer] destroying player"); self.allocator.destroy(self); } const std = @import("std"); const backlog = @import("Backlog"); const core = backlog.core; const rend = backlog.rend; const sdl3 = backlog.platform.windowing.sdl3; const sdl3_c = sdl3.c; const c = @import("c.zig").c; const gpu = rend.renderer.gpu; const ig = backlog.imgui.api;