mirror of
https://github.com/morgan9e/grd
synced 2026-04-14 00:14:18 +09:00
396 lines
14 KiB
C
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;
|
|
}
|