Ctrl K
ComunidadGuías…

server/src/amf/amf_d3d11.h

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
/**
 * @file src/amf/amf_d3d11.h
 * @brief Declarations for AMF D3D11 encoder.
 */
#pragma once

#include "amf_encoder.h"
#include "amf_lifecycle.h"

#include <array>
#include <chrono>
#include <condition_variable>
#include <d3d11.h>
#include <deque>
#include <memory>
#include <mutex>
#include <optional>
#include <string>
#include <thread>
#include <unordered_map>
#include <wrl/client.h>

#include <AMF/components/Component.h>
#include <AMF/core/Context.h>
#include <AMF/core/Data.h>
#include <AMF/core/Factory.h>

namespace amf {

  /**
   * @brief AMF encoder using D3D11 for texture input.
   */
  class amf_d3d11: public amf_encoder {
  public:
    explicit amf_d3d11(ID3D11Device *d3d_device);
    ~amf_d3d11();

    bool
    create_encoder(const amf_config &config,
      const video::config_t &client_config,
      const video::sunshine_colorspace_t &colorspace,
      platf::pix_fmt_e buffer_format) override;

    void
    destroy_encoder() override;

    amf_encode_result
    encode_frame(uint64_t frame_index, bool force_idr) override;

    amf_encode_result
    drain_output(std::chrono::milliseconds timeout) override;

    bool
    has_output_due() override;

    bool
    has_completed_output() override;

    bool
    has_retained_preanalysis_tail() override;

    bool
    begin_drain() override;

    bool
    invalidate_ref_frames(uint64_t first_frame, uint64_t last_frame) override;

    bool
    set_bitrate(int bitrate_kbps) override;

    bool
    set_hdr_metadata(const std::optional<amf_hdr_metadata> &metadata) override;

    void *
    get_input_texture() override;

    ID3D11Texture2D *
    acquire_input_texture_for_render();

    void
    cancel_input_texture_for_render();

  private:
    bool
    init_amf_library();

    bool
    configure_encoder(const amf_config &config,
      const video::config_t &client_config,
      const video::sunshine_colorspace_t &colorspace);

    AMF_SURFACE_FORMAT
    get_amf_format(platf::pix_fmt_e buffer_format, int bit_depth);

    const wchar_t *
    get_codec_id();

    amf_encoded_frame
    extract_encoded_frame(const ::amf::AMFDataPtr &output_data);

    void
    output_pump(std::stop_token stop_token) noexcept;

    void
    on_input_surface_released(std::size_t slot_index, uint64_t generation) noexcept;

    bool
    ensure_input_surface_count(std::size_t count);

    // Safe to call with state_mutex released: device and input_surface_desc are
    // immutable while the encode thread runs. Pool expansion allocates through
    // this so a multi-MB CreateTexture2D never stalls the pump or AMF's
    // release observers behind the lock.
    Microsoft::WRL::ComPtr<ID3D11Texture2D>
    create_input_surface_texture();

    ID3D11Device *device = nullptr;
    ::amf::AMFFactory *factory = nullptr;
    ::amf::AMFContextPtr context;
    ::amf::AMFComponentPtr encoder;
    HMODULE amf_dll = nullptr;

    // The converter renders directly into a reserved pool surface. AMF may retain an
    // accepted native surface while encoding it, so a slot is recycled exclusively by
    // AMFSurfaceObserver. The pool starts at the queue/lookahead-aware working depth
    // and allocates additional slots lazily during a transient driver backlog.
    static constexpr std::size_t INPUT_SURFACE_RING_SIZE = lifecycle::maximum_input_surface_count;
    using input_surface_state_e = lifecycle::input_surface_state_e;

    struct input_surface_release_observer_t final: ::amf::AMFSurfaceObserver {
      amf_d3d11 *owner = nullptr;
      std::size_t slot_index = 0;

      void AMF_STD_CALL OnSurfaceDataRelease(::amf::AMFSurface *surface) override;
    };
    struct input_surface_slot_t: lifecycle::input_surface_state_t {
      Microsoft::WRL::ComPtr<ID3D11Texture2D> texture;
      // Bumped on every reservation; each AMF wrapper is stamped with the value
      // it was created under so a stale OnSurfaceDataRelease from a previous
      // wrapper can never recycle the slot's next occupant.
      uint64_t generation = 0;
    };
    std::array<input_surface_slot_t, INPUT_SURFACE_RING_SIZE> input_surface_ring;
    std::array<input_surface_release_observer_t, INPUT_SURFACE_RING_SIZE> input_surface_release_observers;
    std::size_t active_input_surface_count = lifecycle::minimum_input_surface_count;
    std::size_t encoder_input_queue_size = lifecycle::default_amf_input_queue_size;
    D3D11_TEXTURE2D_DESC input_surface_desc {};
    std::size_t next_input_surface_slot = 0;
    std::optional<std::size_t> prepared_input_surface_slot;
    std::optional<std::size_t> last_rendered_input_surface_slot;

    // Encoder state
    video::config_t current_config {};
    int video_format = 0;  // 0=H264, 1=HEVC, 2=AV1
    AMF_SURFACE_FORMAT surface_format = AMF_SURFACE_NV12;
    int encode_width = 0;
    int encode_height = 0;
    bool rfi_pending = false;
    uint64_t last_rfi_ltr_index = 0;
    int max_ltr_frames = 0;
    bool rfi_enabled = false;

    // Current LTR state for RFI.
    // Slot 0 is reserved as the IDR baseline (set on every IDR, never overwritten by
    // periodic marks) so RFI always has a known-good fallback even when every recent
    // periodic-marked frame was inside a packet-loss window. Slots 1..N-1 form a
    // sliding window of more recent anchors.
    static constexpr int MAX_LTR_SLOTS = 4;
    static constexpr uint64_t LTR_MARK_INTERVAL = 4;  // Mark LTR every N frames
    int effective_ltr_slots = 0;    // Clamped to min(max_ltr_frames, MAX_LTR_SLOTS)
    int current_ltr_slot = 0;      // Which LTR slot to mark next
    std::array<bool, MAX_LTR_SLOTS> ltr_slots_valid {};
    std::array<uint64_t, MAX_LTR_SLOTS> ltr_slot_frame_index {};  // Frame index when each LTR slot was marked

    // QueryOutput is owned by a dedicated pump. This keeps AMF driver completion work
    // off Sunshine's latency-critical encode thread and gives input-slot ownership one
    // synchronization boundary.
    std::mutex state_mutex;
    std::condition_variable state_cv;
    std::jthread output_thread;
    std::deque<amf_encoded_frame> completed_outputs;
    std::unordered_map<uint64_t, bool> frame_rfi_flags;
    uint64_t last_completed_frame_index = 0;
    uint64_t last_submitted_frame_index = 0;
    bool output_fatal = false;
    bool drain_requested = false;
    bool drain_complete = false;
    bool output_poll_requested = false;
    std::size_t active_output_poll_waiters = 0;

    // Input ownership and output production are deliberately tracked separately.
    // AMF explicitly does not guarantee a one-to-one input/output relationship;
    // AMFSurfaceObserver owns the first count while QueryOutput owns the totals.
    std::size_t input_surfaces_in_flight = 0;
    uint64_t accepted_input_count = 0;
    uint64_t completed_output_count = 0;
    std::deque<uint64_t> accepted_frame_indices;
    bool preanalysis_enabled = false;
    int preanalysis_lookahead_depth = 0;
    bool query_timeout_supported = false;
    bool user_configured_rate_control = false;
    std::optional<int> configured_rate_control_mode;
    bool skip_frame_control_verified = false;
    bool enforce_hrd_enabled = false;
    bool max_au_size_supported = false;

    // Statistics feedback state
    bool statistics_enabled = false;
    bool psnr_enabled = false;
    bool ssim_enabled = false;

    // Runtime fault watchdog: count consecutive failures so we can signal
    // a fatal error to the upper layer (triggering a real reinit) instead
    // of silently producing no output forever. Threshold is derived from
    // client framerate in create_encoder() so the watchdog fires after
    // roughly the same wall-clock time regardless of fps.
    int consecutive_submit_failures = 0;
    int consecutive_surface_failures = 0;
    int consecutive_query_failures = 0;
    int consecutive_output_failures = 0;
    // Consecutive submissions whose bounded coalescing target was not reached.
    int consecutive_catchup_misses = 0;
    uint64_t catchup_batch_count = 0;
    int max_consecutive_failures = 60;  // Set to ~1s of frames in create_encoder()
    std::chrono::steady_clock::time_point last_output_progress {};
    std::chrono::steady_clock::time_point submit_backpressure_started {};

    std::string last_error_string;
  };

  /**
   * @brief Create an AMF D3D11 encoder instance.
   * @param d3d_device The D3D11 device to use.
   * @return AMF encoder or nullptr on failure.
   */
  std::unique_ptr<amf_d3d11>
  create_amf_d3d11(ID3D11Device *d3d_device);

}  // namespace amf