diff --git a/src/doompanning.cc b/src/doompanning.cc index b702051..897da53 100644 --- a/src/doompanning.cc +++ b/src/doompanning.cc @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -20,22 +21,64 @@ #include "dp_common.h" #include "dp_util.hpp" +#if DoomBytesPerPixel == 3 +static const u32 DoomSdlTexturePixelFormat = SDL_PIXELFORMAT_RGB888; +#elif DoomBytesPerPixel == 4 +static const u32 DoomSdlTexturePixelFormat = SDL_PIXELFORMAT_ARGB8888; +#else +#error Unhandled DoomBytesPerPixel value. Which SDL_PIXELFORMAT to use? +#endif + + void dp_sdl_fatal(const char *const msg) { log_fatal("%s: %s", msg, SDL_GetError()); abort(); } +void dp_sdl_error(const char *const msg) +{ + log_error("%s: %s", msg, SDL_GetError()); +} + struct DoomState { + // So easy to get this wrong and destroy the texture by accident. Cleanup is + // now done in check_on_dooms() before erasing the state. Enabling this code + // still works though. +#if 0 + DoomState() = default; + ~DoomState() + { + log_trace("~DoomState"); + if (texture) + SDL_DestroyTexture(texture); + } + DoomState(const DoomState &) = delete; + DoomState &operator=(const DoomState &) = delete; + + DoomState(DoomState &&o) + : DoomState() + { + *this = std::move(o); + } + + DoomState &operator=(DoomState &&o) + { + std::swap(id, o.id); + std::swap(state, o.state); + std::swap(tLastActive, o.tLastActive); + std::swap(texture, o.texture); + return *this; + } +#endif + doomid_t id = 0; DP_DoomState state = DP_DS_Unknown; + std::chrono::steady_clock::time_point tLastActive; SDL_Texture *texture = nullptr; }; -static_assert(std::is_trivially_copyable::value, - "DoomState must be a trivially copyable type"); - struct ControllerContext { nng_socket pub; @@ -47,6 +90,10 @@ struct ControllerContext ExampleAppLog appLog; int columns = 4; std::array pixelBuffer; + float scaleFactor = 1.0; + s32 offsetX = 0; + s32 offsetY = 0; + bool isMousePanning = false; }; struct ControllerActions @@ -59,29 +106,22 @@ struct ControllerActions void spawn_doom_posix_spawn(ControllerContext &ctx) { - DoomState ds; - + (void) ctx; const char *const argv[] = { DOOM_EXECUTABLE, nullptr }; // TODO: Close stdin and stdout? Leave them open for now to see the logging // output. - // FIXME: SDL is not able to init its sound system in the doomchild. Try to ask - // it if it's already initialized and skip if true. - if (auto err = posix_spawn(&ds.id, DOOM_EXECUTABLE, nullptr, nullptr, + // FIXME: SDL is not able to init its sound system in the doomchild. Try + // something similar to xdg-open where a doomgrandchild execvp()s the doom. + pid_t pid; + if (auto err = posix_spawn(&pid, DOOM_EXECUTABLE, nullptr, nullptr, const_cast(argv), nullptr)) { log_error("Could not spawn doom: %s", strerror(err)); return; } - ds.texture = SDL_CreateTexture(ctx.renderer, SDL_PIXELFORMAT_RGB888, - SDL_TEXTUREACCESS_STREAMING, DoomScreenWidth, DoomScreenHeight); - - if (!ds.texture) - dp_sdl_fatal("SDL_CreateTexture"); - - log_info("Spawned new doom, pid=%d", ds.id); - ctx.dooms.emplace_back(ds); + log_info("Spawned new doom, pid=%d", pid); } inline void spawn_doom(ControllerContext &ctx) @@ -105,15 +145,6 @@ void end_all_dooms(ControllerContext &ctx) dp_nng_fatal("ctrl/sendmsg", res); } -void signal_all_dooms(ControllerContext &ctx, int signum) -{ - std::for_each(std::begin(ctx.dooms), std::end(ctx.dooms), - [signum] (const auto &ds) - { - kill(ds.id, signum); - }); -} - void perform_actions(ControllerContext &ctx, const ControllerActions &actions) { if (actions.doomsToSpawn) @@ -130,8 +161,12 @@ void perform_actions(ControllerContext &ctx, const ControllerActions &actions) } } +inline auto dp_now() { return std::chrono::steady_clock::now(); } +inline auto dp_elapsed(const std::chrono::steady_clock::time_point &tStart) { return tStart - dp_now(); } + void check_on_dooms(ControllerContext &ctx) { + #if 0 pid_t pid = 0; // This works for dooms forked from the controller, not for externally @@ -158,6 +193,18 @@ void check_on_dooms(ControllerContext &ctx) } } } while (pid > 0); + #else + const auto sz0 = ctx.dooms.size(); + auto eb = std::remove_if(std::begin(ctx.dooms), std::end(ctx.dooms), [] (const auto &ds) { return ds.state == DP_DS_Endoom; }); + if (eb != std::end(ctx.dooms)) + { + auto count = std::distance(eb, std::end(ctx.dooms)); + std::for_each(eb, std::end(ctx.dooms), [] (auto &ds) { SDL_DestroyTexture(ds.texture); ds.texture = nullptr; }); + ctx.dooms.erase(eb, std::end(ctx.dooms)); + const auto sz1 = ctx.dooms.size(); + log_info("Erased %zu dooms which were in Endoom state. doomcount before=%zu, after=%zu", count, sz0, sz1); + } + #endif } void do_networking(ControllerContext &ctx) @@ -168,15 +215,12 @@ void do_networking(ControllerContext &ctx) // Limit the max time we spend doing network stuff. static const auto MaxNetworkingTime = std::chrono::milliseconds(10); - auto tStart = std::chrono::steady_clock::now(); + auto tStart = dp_now(); while (true) { - if (auto elapsed = std::chrono::steady_clock::now() - tStart; - elapsed >= MaxNetworkingTime) - { + if (auto elapsed = dp_elapsed(tStart); elapsed >= MaxNetworkingTime) break; - } nng_msg *msg = nullptr; @@ -193,28 +237,52 @@ void do_networking(ControllerContext &ctx) { auto msgDoomState = DP_NNG_BODY_AS(msg, MsgDoomState); - // Check if we know this doom. If it was externally started register - // it in ctx.dooms. + // Check if we know this doom and register it if we don't. auto pid = msgDoomState->doomId; auto dit = find_in_container(ctx.dooms, [pid] (const auto &ds) { return ds.id == pid; }); if (dit != std::end(ctx.dooms)) { dit->state = msgDoomState->doomState; + dit->tLastActive = dp_now(); } else { DoomState ds; ds.id = pid; ds.state = msgDoomState->doomState; - ds.texture = SDL_CreateTexture(ctx.renderer, SDL_PIXELFORMAT_RGB888, - SDL_TEXTUREACCESS_STREAMING, DoomScreenWidth, DoomScreenHeight); + ds.tLastActive = dp_now(); + ds.texture = SDL_CreateTexture( + ctx.renderer, DoomSdlTexturePixelFormat, + SDL_TEXTUREACCESS_STREAMING, DoomScreenWidth, DoomScreenHeight); if (!ds.texture) dp_sdl_fatal("SDL_CreateTexture"); - log_info("Registered external doom, pid=%d", ds.id); - ctx.dooms.emplace_back(ds); + { + u32 format = 0; + int access = 0; + int w = 0; + int h = 0; + if (SDL_QueryTexture(ds.texture, &format, &access, &w, &h)) + dp_sdl_fatal("SDL_QueryTexture"); + + log_debug("texture info (wanted, got): format=(%u, %u), access=(%d, %d), w=(%d, %d), h=(%d, %d)", + DoomSdlTexturePixelFormat, format, + SDL_TEXTUREACCESS_STREAMING, access, + DoomScreenWidth, w, + DoomScreenHeight, h + ); + + assert(format == DoomSdlTexturePixelFormat); + assert(access == SDL_TEXTUREACCESS_STREAMING); + assert(w == DoomScreenWidth); + assert(h == DoomScreenHeight); + + } + + log_info("Registered new doom (pid=%d)", ds.id); + ctx.dooms.emplace_back(std::move(ds)); } if (msgDoomState->doomState == DP_DS_Ready) @@ -229,10 +297,42 @@ void do_networking(ControllerContext &ctx) if (dit != std::end(ctx.dooms)) { auto &ds = *dit; - SDL_UpdateTexture(ds.texture, nullptr, msgDoomFrame->frame, DoomFramePitch); + ds.tLastActive = dp_now(); + + const u8 *sourcePixels = msgDoomFrame->frame; + + #if 0 + // FIXME: buggy. black screen with tiny bar on top + //log_trace("Texture update for doom (pid=%d, texture=%p)", ds.id, ds.texture); + + u8 *destPixels = nullptr; + int texturePitch = 0; + + if (SDL_LockTexture(ds.texture, nullptr, reinterpret_cast(&destPixels), &texturePitch)) + dp_sdl_fatal("SDL_LockTexture"); + + // When using 3 bytes per pixel (960 bytes per row), SDL yields + // a pitch of 1280 on my machine. This is likely done to get + // good alignment. + assert(DoomFramePitch <= texturePitch); + + for (size_t row=0; row 1 ? "s" : ""); if (ImGui::SameLine(); ImGui::Button(strbuf.data())) result.doomsToSpawn = doomsToSpawn; - if (ImGui::Button("End all Dooms")) + if (ImGui::Button("End all dooms")) result.endAllDooms = true; + ImGui::PopItemWidth(); ImGui::End(); @@ -507,6 +609,9 @@ int doom_controller_loop(ControllerContext &ctx) while (!ctx.quit) { SDL_Event event; + auto &io = ImGui::GetIO(); + s32 mouseWheel = 0.0; + while (SDL_PollEvent(&event)) { ImGui_ImplSDL2_ProcessEvent(&event); @@ -520,18 +625,69 @@ int doom_controller_loop(ControllerContext &ctx) { ctx.quit = true; } + + if (event.type == SDL_MOUSEWHEEL) + mouseWheel += event.wheel.y; } // Process input events not consumed by ImGui - if (auto &io = ImGui::GetIO(); - !io.WantCaptureKeyboard) + if (!io.WantCaptureKeyboard) { + // TODO: make scaling scale with the current scale factor + static const float ScaleStep = 0.01f; + s32 PanStep = io.KeyCtrl ? 10 : 2; + if (io.KeyCtrl && ImGui::IsKeyDown(ImGuiKey_Q)) - { ctx.quit = true; + + if (ImGui::IsKeyDown(ImGuiKey_Equal)) + ctx.scaleFactor += ScaleStep; + if (ImGui::IsKeyDown(ImGuiKey_Minus)) + ctx.scaleFactor -= ScaleStep; + if (ImGui::IsKeyDown(ImGuiKey_0)) + ctx.scaleFactor = 1.0; + + if (ImGui::IsKeyDown(ImGuiKey_H)) + ctx.offsetX -= PanStep; + if (ImGui::IsKeyDown(ImGuiKey_J)) + ctx.offsetY += PanStep; + if (ImGui::IsKeyDown(ImGuiKey_K)) + ctx.offsetY -= PanStep; + if (ImGui::IsKeyDown(ImGuiKey_L)) + ctx.offsetX += PanStep; + + if (ImGui::IsKeyDown(ImGuiKey_G)) + ctx.offsetX = ctx.offsetY = 0; + } + + static ImVec2 panStartPos; + + if (!io.WantCaptureMouse) + { + ctx.scaleFactor += mouseWheel * 0.05; + + if (ImGui::IsKeyPressed(ImGuiKey_MouseLeft, false)) + { + panStartPos = io.MousePos; + ctx.isMousePanning = true; } } + if (ImGui::IsKeyReleased(ImGuiKey_MouseLeft)) + ctx.isMousePanning = false; + + if (ctx.isMousePanning) + { + auto curPos = io.MousePos; + auto dx = curPos.x - panStartPos.x; + auto dy = curPos.y - panStartPos.y; + ctx.offsetX += dx; + ctx.offsetY += dy; + panStartPos = curPos; + } + + ctx.scaleFactor = std::clamp(ctx.scaleFactor, 0.1f, 10.0f); + perform_actions(ctx, actions); check_on_dooms(ctx); do_networking(ctx); @@ -603,7 +759,10 @@ int main(int argc, char *argv[]) ImGui_ImplSDL2_InitForSDLRenderer(window, renderer); ImGui_ImplSDLRenderer_Init(renderer); - dp_nng_init_limits(1, 1, 1); // int ncpu_max, int pool_thread_limit_max, int resolv_thread_limit + //dp_nng_init_limits(1, 1, 1); // int ncpu_max, int pool_thread_limit_max, int resolv_thread_limit + //nng_set_ncpu_max(ncpu_max); + //nng_set_pool_thread_limit_max(pool_thread_limit_max); + nng_set_resolve_thread_max(1); ControllerContext ctx; ctx.pub = make_ctrl_pub(CtrlUrl);