Files
grd/grd-rdp-gfx-frame-controller.c
2026-02-13 13:06:50 +09:00

396 lines
14 KiB
C

/*
* Copyright (C) 2022 Pascal Nowack
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
* 02111-1307, USA.
*/
#include "config.h"
#include "grd-rdp-gfx-frame-controller.h"
#include "grd-rdp-frame-stats.h"
#include "grd-rdp-gfx-frame-log.h"
#include "grd-rdp-gfx-framerate-log.h"
#include "grd-rdp-surface.h"
#include "grd-rdp-surface-renderer.h"
#define ACTIVATE_THROTTLING_TH_DEFAULT 2
#define DEACTIVATE_THROTTLING_TH_DEFAULT 1
#define UNLIMITED_FRAME_SLOTS (UINT32_MAX)
typedef enum _ThrottlingState
{
THROTTLING_STATE_INACTIVE,
THROTTLING_STATE_ACTIVE,
THROTTLING_STATE_ACTIVE_LOWERING_LATENCY,
} ThrottlingState;
struct _GrdRdpGfxFrameController
{
GObject parent;
GrdRdpSurface *rdp_surface;
GrdRdpGfxFrameLog *frame_log;
GrdRdpGfxFramerateLog *framerate_log;
int64_t nw_auto_last_rtt_us;
ThrottlingState throttling_state;
/* Throttling triggers on >= activate_throttling_th */
uint32_t activate_throttling_th;
/* Throttling triggers on <= deactivate_throttling_th */
uint32_t deactivate_throttling_th;
};
G_DEFINE_TYPE (GrdRdpGfxFrameController,
grd_rdp_gfx_frame_controller,
G_TYPE_OBJECT)
GrdRdpGfxFramerateLog *
grd_rdp_gfx_frame_controller_get_framerate_log (GrdRdpGfxFrameController *frame_controller)
{
return frame_controller->framerate_log;
}
static void
notify_frame_stats (GrdRdpGfxFrameController *frame_controller,
uint32_t enc_rate,
uint32_t ack_rate)
{
GrdRdpGfxFrameLog *frame_log = frame_controller->frame_log;
uint32_t missing_dual_frame_acks =
grd_rdp_gfx_frame_log_get_unacked_dual_frames_count (frame_log);
g_autoptr (GrdRdpFrameStats) frame_stats = NULL;
frame_stats = grd_rdp_frame_stats_new (missing_dual_frame_acks,
enc_rate, ack_rate);
grd_rdp_gfx_framerate_log_notify_frame_stats (frame_controller->framerate_log,
frame_stats);
}
void
grd_rdp_gfx_frame_controller_notify_history_changed (GrdRdpGfxFrameController *frame_controller)
{
GrdRdpGfxFrameLog *frame_log = frame_controller->frame_log;
uint32_t enc_rate = 0;
uint32_t ack_rate = 0;
grd_rdp_gfx_frame_log_update_rates (frame_log, &enc_rate, &ack_rate);
notify_frame_stats (frame_controller, enc_rate, ack_rate);
}
static gboolean
is_rendering_suspended (GrdRdpGfxFrameController *frame_controller)
{
GrdRdpSurface *rdp_surface = frame_controller->rdp_surface;
GrdRdpSurfaceRenderer *surface_renderer =
grd_rdp_surface_get_surface_renderer (rdp_surface);
uint32_t total_frame_slots =
grd_rdp_surface_renderer_get_total_frame_slots (surface_renderer);
return total_frame_slots == 0;
}
static uint32_t
get_activate_throttling_th_from_rtt (GrdRdpGfxFrameController *frame_controller,
int64_t rtt_us)
{
GrdRdpSurface *rdp_surface = frame_controller->rdp_surface;
GrdRdpSurfaceRenderer *surface_renderer =
grd_rdp_surface_get_surface_renderer (rdp_surface);
int64_t refresh_rate =
grd_rdp_surface_renderer_get_refresh_rate (surface_renderer);
uint32_t activate_throttling_th;
uint32_t delayed_frames;
delayed_frames = rtt_us * refresh_rate / G_USEC_PER_SEC;
activate_throttling_th = MAX (2, MIN (delayed_frames + 2, refresh_rate));
g_assert (activate_throttling_th > frame_controller->deactivate_throttling_th);
return activate_throttling_th;
}
void
grd_rdp_gfx_frame_controller_unack_frame (GrdRdpGfxFrameController *frame_controller,
uint32_t frame_id,
uint32_t n_subframes,
int64_t enc_time_us)
{
GrdRdpSurface *rdp_surface = frame_controller->rdp_surface;
GrdRdpGfxFrameLog *frame_log = frame_controller->frame_log;
GrdRdpSurfaceRenderer *surface_renderer =
grd_rdp_surface_get_surface_renderer (rdp_surface);
uint32_t current_activate_throttling_th;
uint32_t n_unacked_frames;
uint32_t enc_rate = 0;
uint32_t ack_rate = 0;
grd_rdp_gfx_frame_log_track_frame (frame_log, frame_id, n_subframes,
enc_time_us);
n_unacked_frames = grd_rdp_gfx_frame_log_get_unacked_frames_count (frame_log);
grd_rdp_gfx_frame_log_update_rates (frame_log, &enc_rate, &ack_rate);
notify_frame_stats (frame_controller, enc_rate, ack_rate);
switch (frame_controller->throttling_state)
{
case THROTTLING_STATE_INACTIVE:
frame_controller->activate_throttling_th =
get_activate_throttling_th_from_rtt (frame_controller,
frame_controller->nw_auto_last_rtt_us);
if (n_unacked_frames >= frame_controller->activate_throttling_th)
{
frame_controller->throttling_state = THROTTLING_STATE_ACTIVE;
grd_rdp_surface_renderer_update_total_frame_slots (surface_renderer, 0);
}
break;
case THROTTLING_STATE_ACTIVE:
current_activate_throttling_th =
get_activate_throttling_th_from_rtt (frame_controller,
frame_controller->nw_auto_last_rtt_us);
if (current_activate_throttling_th < frame_controller->activate_throttling_th)
{
frame_controller->throttling_state = THROTTLING_STATE_ACTIVE_LOWERING_LATENCY;
grd_rdp_surface_renderer_update_total_frame_slots (surface_renderer, 0);
}
else
{
uint32_t total_frame_slots;
frame_controller->activate_throttling_th = current_activate_throttling_th;
total_frame_slots = enc_rate > ack_rate + 1 ? 0 : ack_rate + 2 - enc_rate;
grd_rdp_surface_renderer_update_total_frame_slots (surface_renderer,
total_frame_slots);
}
break;
case THROTTLING_STATE_ACTIVE_LOWERING_LATENCY:
g_assert (is_rendering_suspended (frame_controller));
break;
}
}
void
grd_rdp_gfx_frame_controller_ack_frame (GrdRdpGfxFrameController *frame_controller,
uint32_t frame_id,
int64_t ack_time_us)
{
GrdRdpSurface *rdp_surface = frame_controller->rdp_surface;
GrdRdpGfxFrameLog *frame_log = frame_controller->frame_log;
GrdRdpSurfaceRenderer *surface_renderer =
grd_rdp_surface_get_surface_renderer (rdp_surface);
uint32_t current_activate_throttling_th;
uint32_t n_unacked_frames;
uint32_t enc_rate = 0;
uint32_t ack_rate = 0;
grd_rdp_gfx_frame_log_ack_tracked_frame (frame_log, frame_id, ack_time_us);
n_unacked_frames = grd_rdp_gfx_frame_log_get_unacked_frames_count (frame_log);
grd_rdp_gfx_frame_log_update_rates (frame_log, &enc_rate, &ack_rate);
notify_frame_stats (frame_controller, enc_rate, ack_rate);
switch (frame_controller->throttling_state)
{
case THROTTLING_STATE_INACTIVE:
break;
case THROTTLING_STATE_ACTIVE:
if (n_unacked_frames <= frame_controller->deactivate_throttling_th)
{
frame_controller->throttling_state = THROTTLING_STATE_INACTIVE;
grd_rdp_surface_renderer_update_total_frame_slots (surface_renderer,
UNLIMITED_FRAME_SLOTS);
break;
}
current_activate_throttling_th =
get_activate_throttling_th_from_rtt (frame_controller,
frame_controller->nw_auto_last_rtt_us);
if (current_activate_throttling_th < frame_controller->activate_throttling_th)
{
frame_controller->throttling_state = THROTTLING_STATE_ACTIVE_LOWERING_LATENCY;
grd_rdp_surface_renderer_update_total_frame_slots (surface_renderer, 0);
}
else
{
uint32_t total_frame_slots;
frame_controller->activate_throttling_th = current_activate_throttling_th;
total_frame_slots = enc_rate > ack_rate ? 0 : ack_rate + 1 - enc_rate;
grd_rdp_surface_renderer_update_total_frame_slots (surface_renderer,
total_frame_slots);
}
break;
case THROTTLING_STATE_ACTIVE_LOWERING_LATENCY:
current_activate_throttling_th =
get_activate_throttling_th_from_rtt (frame_controller,
frame_controller->nw_auto_last_rtt_us);
if (n_unacked_frames < current_activate_throttling_th)
{
frame_controller->throttling_state = THROTTLING_STATE_INACTIVE;
grd_rdp_surface_renderer_update_total_frame_slots (surface_renderer,
UNLIMITED_FRAME_SLOTS);
}
else if (n_unacked_frames == current_activate_throttling_th)
{
uint32_t total_frame_slots;
frame_controller->throttling_state = THROTTLING_STATE_ACTIVE;
total_frame_slots = enc_rate > ack_rate ? 0 : ack_rate + 1 - enc_rate;
grd_rdp_surface_renderer_update_total_frame_slots (surface_renderer,
total_frame_slots);
}
else if (n_unacked_frames > current_activate_throttling_th)
{
g_assert (is_rendering_suspended (frame_controller));
}
else
{
g_assert_not_reached ();
}
break;
}
}
static void
reevaluate_encoding_suspension_state (GrdRdpGfxFrameController *frame_controller)
{
GrdRdpSurface *rdp_surface = frame_controller->rdp_surface;
GrdRdpGfxFrameLog *frame_log = frame_controller->frame_log;
GrdRdpSurfaceRenderer *surface_renderer =
grd_rdp_surface_get_surface_renderer (rdp_surface);
uint32_t n_unacked_frames;
uint32_t enc_rate = 0;
uint32_t ack_rate = 0;
n_unacked_frames = grd_rdp_gfx_frame_log_get_unacked_frames_count (frame_log);
grd_rdp_gfx_frame_log_update_rates (frame_log, &enc_rate, &ack_rate);
switch (frame_controller->throttling_state)
{
case THROTTLING_STATE_INACTIVE:
frame_controller->activate_throttling_th =
get_activate_throttling_th_from_rtt (frame_controller,
frame_controller->nw_auto_last_rtt_us);
if (n_unacked_frames >= frame_controller->activate_throttling_th)
{
frame_controller->throttling_state = THROTTLING_STATE_ACTIVE;
grd_rdp_surface_renderer_update_total_frame_slots (surface_renderer, 0);
}
break;
case THROTTLING_STATE_ACTIVE:
g_assert (frame_controller->activate_throttling_th >
frame_controller->deactivate_throttling_th);
g_assert (n_unacked_frames > frame_controller->deactivate_throttling_th);
g_assert (is_rendering_suspended (frame_controller));
break;
case THROTTLING_STATE_ACTIVE_LOWERING_LATENCY:
/*
* While the graphics pipeline rewrites the frame history, the RTT
* detection mechanism cannot submit a new round trip time.
*/
g_assert_not_reached ();
break;
}
}
void
grd_rdp_gfx_frame_controller_unack_last_acked_frame (GrdRdpGfxFrameController *frame_controller,
uint32_t frame_id,
uint32_t n_subframes,
int64_t enc_ack_time_us)
{
grd_rdp_gfx_frame_log_unack_last_acked_frame (frame_controller->frame_log,
frame_id, n_subframes,
enc_ack_time_us);
reevaluate_encoding_suspension_state (frame_controller);
}
void
grd_rdp_gfx_frame_controller_clear_all_unacked_frames (GrdRdpGfxFrameController *frame_controller)
{
GrdRdpSurface *rdp_surface = frame_controller->rdp_surface;
GrdRdpSurfaceRenderer *surface_renderer =
grd_rdp_surface_get_surface_renderer (rdp_surface);
grd_rdp_gfx_frame_log_clear (frame_controller->frame_log);
frame_controller->throttling_state = THROTTLING_STATE_INACTIVE;
grd_rdp_surface_renderer_update_total_frame_slots (surface_renderer,
UNLIMITED_FRAME_SLOTS);
}
void
grd_rdp_gfx_frame_controller_notify_new_round_trip_time (GrdRdpGfxFrameController *frame_controller,
int64_t round_trip_time_us)
{
frame_controller->nw_auto_last_rtt_us = round_trip_time_us;
}
GrdRdpGfxFrameController *
grd_rdp_gfx_frame_controller_new (GrdRdpSurface *rdp_surface)
{
GrdRdpGfxFrameController *frame_controller;
frame_controller = g_object_new (GRD_TYPE_RDP_GFX_FRAME_CONTROLLER, NULL);
frame_controller->rdp_surface = rdp_surface;
return frame_controller;
}
static void
grd_rdp_gfx_frame_controller_dispose (GObject *object)
{
GrdRdpGfxFrameController *frame_controller = GRD_RDP_GFX_FRAME_CONTROLLER (object);
g_clear_object (&frame_controller->framerate_log);
g_clear_object (&frame_controller->frame_log);
G_OBJECT_CLASS (grd_rdp_gfx_frame_controller_parent_class)->dispose (object);
}
static void
grd_rdp_gfx_frame_controller_init (GrdRdpGfxFrameController *frame_controller)
{
frame_controller->throttling_state = THROTTLING_STATE_INACTIVE;
frame_controller->activate_throttling_th = ACTIVATE_THROTTLING_TH_DEFAULT;
frame_controller->deactivate_throttling_th = DEACTIVATE_THROTTLING_TH_DEFAULT;
g_assert (frame_controller->activate_throttling_th >
frame_controller->deactivate_throttling_th);
frame_controller->frame_log = grd_rdp_gfx_frame_log_new ();
frame_controller->framerate_log = grd_rdp_gfx_framerate_log_new ();
}
static void
grd_rdp_gfx_frame_controller_class_init (GrdRdpGfxFrameControllerClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->dispose = grd_rdp_gfx_frame_controller_dispose;
}