// DXGI Desktop Duplication frame source — M3 step 1 (docs/m3-product-shape.md). // // Captures one monitor via IDXGIOutputDuplication on a private D3D11 device // (same adapter as the render device), composites the pointer shape, builds // a mip chain (GenerateMips — a 4K desktop on a 2 m quad is heavily // minified), and publishes into shared textures the D3D12 render device // opened at start. // // Resilience (M1 invariants extended): ACCESS_LOST — display-mode change, // UAC secure desktop, fullscreen exclusive — drops into a reacquire loop // with backoff; never wedges, never affects tracking. A mode change that // alters the desktop size recreates the texture set and bumps generation(). #pragma once #include "capture/frame_source.h" #include #include namespace sauna { class DuplicationSource : public FrameSource { public: // outputIndex = DXGI EnumOutputs index on the render adapter. explicit DuplicationSource(int outputIndex = 0); ~DuplicationSource() override; bool start(ID3D12Device* renderDev) override; void stop() override; // releases ALL GPU resources (frees VRAM); keeps object void setPaused(bool paused) override; // SteamVR coexistence park // Doze VRAM lever (M5): rebuild the GPU side (D3D11 device + ring + worker) // after stop() freed it. Reuses the cached render device. Call only while the // present loop is idle (dozing) so the consumer is not sampling mid-rebuild; // the consumer skips the source (published = -1) until the first frame // republishes, then the generation bump triggers an SRV rebuild. bool resume(); // Rate cap (M4 step 3): process at most one desktop frame per this many // ms (0 = every frame). The copy+cursor+GenerateMips pipeline runs per // captured frame; content redrawing faster than the panel refresh costs // pure GPU contention for zero visible benefit. Set before start(); // the app passes the panel period. void setMinFramePeriodMs(double ms); // Cursor overlay policy: -1 auto (= on), 0 never (for machines whose // duplicated image already contains a software cursor — overlay would // double it), 1 always. Live-settable. void setCursorOverlay(int mode); uint32_t width() const override; uint32_t height() const override; uint32_t mipLevels() const override; uint32_t textureCount() const override; ID3D12Resource* texture(uint32_t i) const override; int latest(uint32_t* gen) const override; struct Stats { uint64_t frames = 0; // desktop frames captured + published uint64_t reacquires = 0; // duplication losses survived bool capturing = false; // false while in the reacquire loop }; Stats stats() const; private: struct Impl; std::unique_ptr impl_; }; } // namespace sauna