mirror of
https://github.com/ultravideo/uvg266.git
synced 2024-11-30 12:44:07 +00:00
[LMCS] initial bitstream writing and LMCS structures
This commit is contained in:
parent
3d9d1930d8
commit
38eafbbf78
|
@ -100,6 +100,8 @@ libkvazaar_la_SOURCES = \
|
||||||
rate_control.h \
|
rate_control.h \
|
||||||
rdo.c \
|
rdo.c \
|
||||||
rdo.h \
|
rdo.h \
|
||||||
|
reshape.c \
|
||||||
|
reshape.h \
|
||||||
sao.c \
|
sao.c \
|
||||||
sao.h \
|
sao.h \
|
||||||
scalinglist.c \
|
scalinglist.c \
|
||||||
|
|
52
src/alf.c
52
src/alf.c
|
@ -11,6 +11,7 @@
|
||||||
#include "rdo.h"
|
#include "rdo.h"
|
||||||
#include "strategies/strategies-sao.h"
|
#include "strategies/strategies-sao.h"
|
||||||
#include "kvz_math.h"
|
#include "kvz_math.h"
|
||||||
|
#include "reshape.h"
|
||||||
|
|
||||||
#if MAX_NUM_CC_ALF_FILTERS>1
|
#if MAX_NUM_CC_ALF_FILTERS>1
|
||||||
typedef struct filter_idx_count
|
typedef struct filter_idx_count
|
||||||
|
@ -1574,6 +1575,42 @@ static void encode_alf_aps_flags(encoder_state_t * const state,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ToDo: Fill in LMCS APS
|
||||||
|
static void encode_lmcs_aps(encoder_state_t* const state, lmcs_aps* aps)
|
||||||
|
{
|
||||||
|
bitstream_t* const stream = &state->stream;
|
||||||
|
//SliceReshapeInfo param = pcAPS->getReshaperAPSInfo();
|
||||||
|
WRITE_UE(stream, 0/*param.reshaperModelMinBinIdx*/, "lmcs_min_bin_idx");
|
||||||
|
WRITE_UE(stream, 16 - 1/*16 - 1 - param.reshaperModelMaxBinIdx*/, "lmcs_delta_max_bin_idx");
|
||||||
|
|
||||||
|
WRITE_UE(stream, 7/*param.maxNbitsNeededDeltaCW - 1*/, "lmcs_delta_cw_prec_minus1");
|
||||||
|
/*
|
||||||
|
for (int i = param.reshaperModelMinBinIdx; i <= param.reshaperModelMaxBinIdx; i++)
|
||||||
|
{
|
||||||
|
int deltaCW = param.reshaperModelBinCWDelta[i];
|
||||||
|
int signCW = (deltaCW < 0) ? 1 : 0;
|
||||||
|
int absCW = (deltaCW < 0) ? (-deltaCW) : deltaCW;
|
||||||
|
WRITE_CODE(absCW, param.maxNbitsNeededDeltaCW, "lmcs_delta_abs_cw[ i ]");
|
||||||
|
if (absCW > 0)
|
||||||
|
{
|
||||||
|
WRITE_FLAG(signCW, "lmcs_delta_sign_cw_flag[ i ]");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int deltaCRS = pcAPS->chromaPresentFlag ? param.chrResScalingOffset : 0;
|
||||||
|
int signCRS = (deltaCRS < 0) ? 1 : 0;
|
||||||
|
int absCRS = (deltaCRS < 0) ? (-deltaCRS) : deltaCRS;
|
||||||
|
if (pcAPS->chromaPresentFlag)
|
||||||
|
{
|
||||||
|
WRITE_CODE(absCRS, 3, "lmcs_delta_abs_crs");
|
||||||
|
}
|
||||||
|
if (absCRS > 0)
|
||||||
|
{
|
||||||
|
WRITE_FLAG(signCRS, "lmcs_delta_sign_crs_flag");
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
static void encoder_state_write_adaptation_parameter_set(encoder_state_t * const state, alf_aps *aps)
|
static void encoder_state_write_adaptation_parameter_set(encoder_state_t * const state, alf_aps *aps)
|
||||||
{
|
{
|
||||||
#ifdef KVZ_DEBUG
|
#ifdef KVZ_DEBUG
|
||||||
|
@ -1590,10 +1627,10 @@ static void encoder_state_write_adaptation_parameter_set(encoder_state_t * const
|
||||||
{
|
{
|
||||||
encode_alf_aps_flags(state, aps);
|
encode_alf_aps_flags(state, aps);
|
||||||
}
|
}
|
||||||
/*else if (aps->aps_type == T_LMCS_APS)
|
else if (aps->aps_type == T_LMCS_APS)
|
||||||
{
|
{
|
||||||
codeLmcsAps(pcAPS);
|
//encode_lmcs_aps(state);
|
||||||
}*/
|
}
|
||||||
/*else if (aps->aps_type == T_SCALING_LIST_APS)
|
/*else if (aps->aps_type == T_SCALING_LIST_APS)
|
||||||
{
|
{
|
||||||
codeScalingListAps(pcAPS);
|
codeScalingListAps(pcAPS);
|
||||||
|
@ -1606,7 +1643,7 @@ static void encoder_state_write_adaptation_parameter_set(encoder_state_t * const
|
||||||
//For example, in RA, update is on intra slice, but intra slice may not use reshaper
|
//For example, in RA, update is on intra slice, but intra slice may not use reshaper
|
||||||
static void encode_alf_aps_lmcs(encoder_state_t * const state)
|
static void encode_alf_aps_lmcs(encoder_state_t * const state)
|
||||||
{
|
{
|
||||||
if (0 /*pcSlice->getSPS()->getUseLmcs()*/)
|
if (state->encoder_control->cfg.lmcs_enable) // ToDo: do something with LMCS
|
||||||
{/*
|
{/*
|
||||||
//only 1 LMCS data for 1 picture
|
//only 1 LMCS data for 1 picture
|
||||||
int apsId = picHeader->getLmcsAPSId();
|
int apsId = picHeader->getLmcsAPSId();
|
||||||
|
@ -1670,18 +1707,11 @@ static void encode_alf_aps(encoder_state_t * const state)
|
||||||
|
|
||||||
if (write_aps)
|
if (write_aps)
|
||||||
{
|
{
|
||||||
//actualTotalBits += xWriteAPS(accessUnit, aps);
|
|
||||||
kvz_nal_write(stream, NAL_UNIT_PREFIX_APS, 0, state->frame->first_nal);
|
kvz_nal_write(stream, NAL_UNIT_PREFIX_APS, 0, state->frame->first_nal);
|
||||||
state->frame->first_nal = false;
|
state->frame->first_nal = false;
|
||||||
encoder_state_write_adaptation_parameter_set(state, &aps);
|
encoder_state_write_adaptation_parameter_set(state, &aps);
|
||||||
|
|
||||||
//apsMap->clearChangedFlag((apsId << NUM_APS_TYPE_LEN) + ALF_APS);
|
|
||||||
aps_map[aps_id + T_ALF_APS].b_changed = false;
|
aps_map[aps_id + T_ALF_APS].b_changed = false;
|
||||||
|
|
||||||
//CHECK(aps != pcSlice->getAlfAPSs()[apsId] && apsId != pcSlice->getTileGroupCcAlfCbApsId() && apsId != pcSlice->getTileGroupCcAlfCrApsId(), "Wrong APS pointer in compressGOP");
|
|
||||||
/*assert(!(aps.aps_id != state->slice->apss[aps_id].aps_id
|
|
||||||
&& aps_id != state->slice->tile_group_cc_alf_cr_aps_id
|
|
||||||
&& aps_id != state->slice->tile_group_cc_alf_cr_aps_id)); //"Wrong APS id");*/
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -631,7 +631,7 @@ static void encoder_state_write_bitstream_seq_parameter_set(bitstream_t* stream,
|
||||||
WRITE_U(stream, encoder->cfg.alf_type == KVZ_ALF_FULL, 1, "sps_ccalf_enabled_flag");
|
WRITE_U(stream, encoder->cfg.alf_type == KVZ_ALF_FULL, 1, "sps_ccalf_enabled_flag");
|
||||||
}
|
}
|
||||||
|
|
||||||
WRITE_U(stream, 0, 1, "sps_lmcs_enable_flag");
|
WRITE_U(stream, encoder->cfg.lmcs_enable, 1, "sps_lmcs_enable_flag");
|
||||||
|
|
||||||
WRITE_U(stream, 0, 1, "sps_weighted_pred_flag"); // Use of Weighting Prediction (P_SLICE)
|
WRITE_U(stream, 0, 1, "sps_weighted_pred_flag"); // Use of Weighting Prediction (P_SLICE)
|
||||||
WRITE_U(stream, 0, 1, "sps_weighted_bipred_flag"); // Use of Weighting Bi-Prediction (B_SLICE)
|
WRITE_U(stream, 0, 1, "sps_weighted_bipred_flag"); // Use of Weighting Bi-Prediction (B_SLICE)
|
||||||
|
@ -1111,7 +1111,20 @@ static void kvz_encoder_state_write_bitstream_picture_header(
|
||||||
state->tile->frame->alf_cc_enable_flag[COMPONENT_Cb] = false;
|
state->tile->frame->alf_cc_enable_flag[COMPONENT_Cb] = false;
|
||||||
state->tile->frame->alf_cc_enable_flag[COMPONENT_Cr] = false;*/
|
state->tile->frame->alf_cc_enable_flag[COMPONENT_Cr] = false;*/
|
||||||
}
|
}
|
||||||
|
if (encoder->cfg.lmcs_enable)
|
||||||
|
{
|
||||||
|
WRITE_U(stream, 1, 1, "ph_lmcs_enabled_flag");
|
||||||
|
|
||||||
|
//if (picHeader->getLmcsEnabledFlag())
|
||||||
|
{
|
||||||
|
WRITE_U(stream, 0, 2, "ph_lmcs_aps_id");
|
||||||
|
|
||||||
|
if (encoder->chroma_format != KVZ_CSP_400)
|
||||||
|
{
|
||||||
|
WRITE_U(stream, 0, 1, "ph_chroma_residual_scale_flag"); // ToDo: LMCS Enable chroma scaling
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
// getDeblockingFilterControlPresentFlag
|
// getDeblockingFilterControlPresentFlag
|
||||||
|
|
||||||
// END PICTURE HEADER
|
// END PICTURE HEADER
|
||||||
|
@ -1494,6 +1507,15 @@ static void encoder_state_write_bitstream_main(encoder_state_t * const state)
|
||||||
// Adaptation parameter set (APS)
|
// Adaptation parameter set (APS)
|
||||||
kvz_encode_alf_adaptive_parameter_set(state);
|
kvz_encode_alf_adaptive_parameter_set(state);
|
||||||
|
|
||||||
|
if (state->encoder_control->cfg.lmcs_enable) {
|
||||||
|
// ToDo: Write LMCS APS NAL
|
||||||
|
/*
|
||||||
|
kvz_nal_write(stream, NAL_UNIT_PREFIX_APS, 0, state->frame->first_nal);
|
||||||
|
state->frame->first_nal = false;
|
||||||
|
encoder_state_write_adaptation_parameter_set(state, &aps);
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
encoder_state_write_bitstream_children(state);
|
encoder_state_write_bitstream_children(state);
|
||||||
|
|
||||||
if (state->encoder_control->cfg.hash != KVZ_HASH_NONE) {
|
if (state->encoder_control->cfg.hash != KVZ_HASH_NONE) {
|
||||||
|
|
|
@ -53,6 +53,12 @@
|
||||||
// VVC related definitions
|
// VVC related definitions
|
||||||
#define ENABLE_WPP_PARALLELISM 0
|
#define ENABLE_WPP_PARALLELISM 0
|
||||||
|
|
||||||
|
//LMCS
|
||||||
|
#define PIC_CODE_CW_BINS 16
|
||||||
|
#define PIC_ANALYZE_CW_BINS 32
|
||||||
|
#define LMCS_SEG_NUM 32
|
||||||
|
#define FP_PREC 11
|
||||||
|
#define CSCALE_FP_PREC 11
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \defgroup Bitstream
|
* \defgroup Bitstream
|
||||||
|
|
97
src/reshape.c
Normal file
97
src/reshape.c
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
/*****************************************************************************
|
||||||
|
* This file is part of Kvazaar HEVC encoder.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2013-2021 Tampere University of Technology and others (see
|
||||||
|
* COPYING file).
|
||||||
|
*
|
||||||
|
* Kvazaar is free software: you can redistribute it and/or modify it under
|
||||||
|
* the terms of the GNU Lesser General Public License as published by the
|
||||||
|
* Free Software Foundation; either version 2.1 of the License, or (at your
|
||||||
|
* option) any later version.
|
||||||
|
*
|
||||||
|
* Kvazaar 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 Lesser General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along
|
||||||
|
* with Kvazaar. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
#include "reshape.h"
|
||||||
|
|
||||||
|
#include <limits.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
#include "cabac.h"
|
||||||
|
#include "rdo.h"
|
||||||
|
#include "strategies/strategies-sao.h"
|
||||||
|
#include "kvz_math.h"
|
||||||
|
|
||||||
|
void kvz_free_lmcs_aps(lmcs_aps* aps)
|
||||||
|
{
|
||||||
|
FREE_POINTER(aps->m_invLUT);
|
||||||
|
FREE_POINTER(aps->m_fwdLUT);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void kvz_init_lmcs_seq_stats(lmcs_seq_info* stats, int32_t m_binNum)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < m_binNum; i++)
|
||||||
|
{
|
||||||
|
stats->binVar[i] = 0.0;
|
||||||
|
stats->binHist[i] = 0.0;
|
||||||
|
stats->normVar[i] = 0.0;
|
||||||
|
}
|
||||||
|
stats->nonZeroCnt = 0;
|
||||||
|
stats->weightVar = 0.0;
|
||||||
|
stats->weightNorm = 0.0;
|
||||||
|
stats->minBinVar = 0.0;
|
||||||
|
stats->maxBinVar = 0.0;
|
||||||
|
stats->meanBinVar = 0.0;
|
||||||
|
stats->ratioStdU = 0.0;
|
||||||
|
stats->ratioStdV = 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void kvz_init_lmcs_aps(lmcs_aps* aps, int picWidth, int picHeight, uint32_t maxCUWidth, uint32_t maxCUHeight, int bitDepth)
|
||||||
|
{
|
||||||
|
aps->m_lumaBD = bitDepth;
|
||||||
|
aps->m_reshapeLUTSize = 1 << aps->m_lumaBD;
|
||||||
|
aps->m_initCWAnalyze = aps->m_reshapeLUTSize / PIC_ANALYZE_CW_BINS;
|
||||||
|
aps->m_initCW = aps->m_reshapeLUTSize / PIC_CODE_CW_BINS;
|
||||||
|
|
||||||
|
aps->m_fwdLUT = calloc(1, sizeof(kvz_pixel) * aps->m_reshapeLUTSize);
|
||||||
|
|
||||||
|
aps->m_invLUT = calloc(1, sizeof(kvz_pixel) * aps->m_reshapeLUTSize);
|
||||||
|
|
||||||
|
memset(aps->m_binCW, 0, sizeof(uint16_t) * PIC_ANALYZE_CW_BINS);
|
||||||
|
memset(aps->m_binImportance, 0, sizeof(uint32_t) * PIC_ANALYZE_CW_BINS);
|
||||||
|
memset(aps->m_reshapePivot, 0, sizeof(kvz_pixel) * PIC_CODE_CW_BINS + 1);
|
||||||
|
memset(aps->m_inputPivot, 0, sizeof(kvz_pixel) * PIC_CODE_CW_BINS + 1);
|
||||||
|
|
||||||
|
for (int i = 0; i < PIC_CODE_CW_BINS; i++) {
|
||||||
|
aps->m_fwdScaleCoef[i] = 1 << FP_PREC;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < PIC_CODE_CW_BINS; i++) {
|
||||||
|
aps->m_invScaleCoef[i] = 1 << FP_PREC;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < PIC_CODE_CW_BINS; i++) {
|
||||||
|
aps->m_chromaAdjHelpLUT[i] = 1 << CSCALE_FP_PREC;
|
||||||
|
}
|
||||||
|
|
||||||
|
aps->m_sliceReshapeInfo.sliceReshaperEnableFlag = true;
|
||||||
|
aps->m_sliceReshapeInfo.enableChromaAdj = true;
|
||||||
|
aps->m_sliceReshapeInfo.sliceReshaperModelPresentFlag = true;
|
||||||
|
aps->m_sliceReshapeInfo.reshaperModelMinBinIdx = 0;
|
||||||
|
aps->m_sliceReshapeInfo.reshaperModelMaxBinIdx = PIC_CODE_CW_BINS - 1;
|
||||||
|
memset(aps->m_sliceReshapeInfo.reshaperModelBinCWDelta, 0, (PIC_CODE_CW_BINS) * sizeof(int));
|
||||||
|
aps->m_sliceReshapeInfo.chrResScalingOffset = 0;
|
||||||
|
|
||||||
|
aps->m_binNum = PIC_CODE_CW_BINS;
|
||||||
|
kvz_init_lmcs_seq_stats(&aps->m_srcSeqStats, aps->m_binNum);
|
||||||
|
kvz_init_lmcs_seq_stats(&aps->m_rspSeqStats, aps->m_binNum);
|
||||||
|
}
|
105
src/reshape.h
Normal file
105
src/reshape.h
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
#pragma once
|
||||||
|
/*****************************************************************************
|
||||||
|
* This file is part of Kvazaar HEVC encoder.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2013-2021 Tampere University of Technology and others (see
|
||||||
|
* COPYING file).
|
||||||
|
*
|
||||||
|
* Kvazaar is free software: you can redistribute it and/or modify it under
|
||||||
|
* the terms of the GNU Lesser General Public License as published by the
|
||||||
|
* Free Software Foundation; either version 2.1 of the License, or (at your
|
||||||
|
* option) any later version.
|
||||||
|
*
|
||||||
|
* Kvazaar 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 Lesser General Public License for
|
||||||
|
* more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License along
|
||||||
|
* with Kvazaar. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \ingroup Reconstruction
|
||||||
|
* \file
|
||||||
|
* LMCS reshape.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "checkpoint.h"
|
||||||
|
#include "cu.h"
|
||||||
|
#include "encoder.h"
|
||||||
|
#include "encoderstate.h"
|
||||||
|
#include "global.h" // IWYU pragma: keep
|
||||||
|
#include "kvazaar.h"
|
||||||
|
#include "videoframe.h"
|
||||||
|
#include "image.h"
|
||||||
|
#include "nal.h"
|
||||||
|
|
||||||
|
typedef struct lmcs_seq_info
|
||||||
|
{
|
||||||
|
double binVar[32];
|
||||||
|
double binHist[32];
|
||||||
|
double normVar[32];
|
||||||
|
int nonZeroCnt;
|
||||||
|
double weightVar;
|
||||||
|
double weightNorm;
|
||||||
|
double minBinVar;
|
||||||
|
double maxBinVar;
|
||||||
|
double meanBinVar;
|
||||||
|
double ratioStdU;
|
||||||
|
double ratioStdV;
|
||||||
|
} lmcs_seq_info;
|
||||||
|
|
||||||
|
typedef struct SliceReshapeInfo {
|
||||||
|
bool sliceReshaperEnableFlag;
|
||||||
|
bool sliceReshaperModelPresentFlag;
|
||||||
|
unsigned enableChromaAdj;
|
||||||
|
uint32_t reshaperModelMinBinIdx;
|
||||||
|
uint32_t reshaperModelMaxBinIdx;
|
||||||
|
int reshaperModelBinCWDelta[PIC_CODE_CW_BINS];
|
||||||
|
int maxNbitsNeededDeltaCW;
|
||||||
|
int chrResScalingOffset;
|
||||||
|
} SliceReshapeInfo;
|
||||||
|
|
||||||
|
typedef struct lmcs_aps {
|
||||||
|
SliceReshapeInfo m_sliceReshapeInfo;
|
||||||
|
bool m_CTUFlag;
|
||||||
|
bool m_recReshaped;
|
||||||
|
kvz_pixel* m_invLUT;
|
||||||
|
kvz_pixel* m_fwdLUT;
|
||||||
|
int32_t m_chromaAdjHelpLUT[PIC_CODE_CW_BINS];
|
||||||
|
uint16_t m_binCW[PIC_ANALYZE_CW_BINS];
|
||||||
|
uint16_t m_initCW;
|
||||||
|
bool m_reshape;
|
||||||
|
kvz_pixel m_reshapePivot[PIC_CODE_CW_BINS + 1];
|
||||||
|
kvz_pixel m_inputPivot[PIC_CODE_CW_BINS + 1];
|
||||||
|
int32_t m_fwdScaleCoef[PIC_CODE_CW_BINS];
|
||||||
|
int32_t m_invScaleCoef[PIC_CODE_CW_BINS];
|
||||||
|
int m_lumaBD;
|
||||||
|
int m_reshapeLUTSize;
|
||||||
|
int m_chromaScale;
|
||||||
|
int m_vpduX;
|
||||||
|
int m_vpduY;
|
||||||
|
|
||||||
|
bool m_exceedSTD;
|
||||||
|
uint32_t m_binImportance[PIC_ANALYZE_CW_BINS];
|
||||||
|
int m_tcase;
|
||||||
|
int m_rateAdpMode;
|
||||||
|
bool m_useAdpCW;
|
||||||
|
uint16_t m_initCWAnalyze;
|
||||||
|
//ReshapeCW m_reshapeCW;
|
||||||
|
kvz_pixel m_cwLumaWeight[PIC_CODE_CW_BINS];
|
||||||
|
double m_chromaWeight;
|
||||||
|
int m_chromaAdj;
|
||||||
|
int m_binNum;
|
||||||
|
lmcs_seq_info m_srcSeqStats;
|
||||||
|
lmcs_seq_info m_rspSeqStats;
|
||||||
|
|
||||||
|
} lmcs_aps;
|
||||||
|
|
||||||
|
|
||||||
|
void kvz_free_lmcs_aps(lmcs_aps* aps);
|
||||||
|
|
||||||
|
void kvz_init_lmcs_seq_stats(lmcs_seq_info* stats, int32_t m_binNum);
|
||||||
|
|
||||||
|
void kvz_init_lmcs_aps(lmcs_aps* aps, int picWidth, int picHeight, uint32_t maxCUWidth, uint32_t maxCUHeight, int bitDepth);
|
Loading…
Reference in a new issue