graphics: Add display color sampling interface.
Add a graphics.composer@2.3 interface that will expose color sampling hardware present on some devices to the framework. Adds: getDisplayedContentSamplingAttributes setDisplayedContentSamplingEnabled getDisplayedContentSample Test: boot up pixel3 Test: VtsHalGraphicsComposerV2_3TargetTest on revved Pixel3 hwcomposer Bug: 116028976 Change-Id: I88455f200590926f677c47efc39e9b6678e2318c
This commit is contained in:
parent
1028dd74bf
commit
bf141483fa
7 changed files with 352 additions and 0 deletions
|
@ -161,6 +161,116 @@ interface IComposerClient extends @2.2::IComposerClient {
|
|||
Dataspace dataspace)
|
||||
generates (Error error);
|
||||
|
||||
enum FormatColorComponent : uint8_t {
|
||||
/* The first component (eg, for RGBA_8888, this is R) */
|
||||
FORMAT_COMPONENT_0 = 1 << 0,
|
||||
/* The second component (eg, for RGBA_8888, this is G) */
|
||||
FORMAT_COMPONENT_1 = 1 << 1,
|
||||
/* The third component (eg, for RGBA_8888, this is B) */
|
||||
FORMAT_COMPONENT_2 = 1 << 2,
|
||||
/* The fourth component (eg, for RGBA_8888, this is A) */
|
||||
FORMAT_COMPONENT_3 = 1 << 3,
|
||||
};
|
||||
|
||||
/**
|
||||
* Query for what types of color sampling the hardware supports.
|
||||
*
|
||||
* @param display is the display where the samples are collected.
|
||||
* @return error is NONE upon success. Otherwise,
|
||||
* BAD_DISPLAY when an invalid display was passed in, or
|
||||
* UNSUPPORTED when there is no efficient way to sample.
|
||||
* @return format The format of the sampled pixels.
|
||||
* @return dataspace The dataspace of the sampled pixels.
|
||||
* @return componentMask The mask of which components can be sampled.
|
||||
*/
|
||||
getDisplayedContentSamplingAttributes(Display display)
|
||||
generates (Error error,
|
||||
PixelFormat format,
|
||||
Dataspace dataspace,
|
||||
bitfield<FormatColorComponent> componentMask);
|
||||
|
||||
/** DisplayedContentSampling values passed to setDisplayedContentSamplingEnabled. */
|
||||
enum DisplayedContentSampling : int32_t {
|
||||
INVALID = 0,
|
||||
|
||||
/** Enable content sampling. */
|
||||
ENABLE = 1,
|
||||
|
||||
/** Disable content sampling. */
|
||||
DISABLE = 2,
|
||||
};
|
||||
|
||||
/**
|
||||
* Enables or disables the collection of color content statistics
|
||||
* on this display.
|
||||
*
|
||||
* Sampling occurs on the contents of the final composition on this display
|
||||
* (i.e., the contents presented on screen). Samples should be collected after all
|
||||
* color transforms have been applied.
|
||||
*
|
||||
* Sampling support is optional, and is set to DISABLE by default.
|
||||
* On each call to ENABLE, all collected statistics must be reset.
|
||||
*
|
||||
* Sample data can be queried via getDisplayedContentSample().
|
||||
*
|
||||
* @param display is the display to which the sampling mode is set.
|
||||
* @param enabled indicates whether to enable or disable sampling.
|
||||
* @param componentMask The mask of which components should be sampled. If zero, all supported
|
||||
* components are to be enabled.
|
||||
* @param maxFrames is the maximum number of frames that should be stored before discard.
|
||||
* The sample represents the most-recently posted frames.
|
||||
* @return error is NONE upon success. Otherwise,
|
||||
* BAD_DISPLAY when an invalid display handle was passed in,
|
||||
* BAD_PARAMETER when enabled was an invalid value, or
|
||||
* NO_RESOURCES when the requested ringbuffer size via maxFrames was
|
||||
* not available.
|
||||
* UNSUPPORTED when there is no efficient way to sample.
|
||||
*/
|
||||
setDisplayedContentSamplingEnabled(
|
||||
Display display, DisplayedContentSampling enable,
|
||||
bitfield<FormatColorComponent> componentMask, uint64_t maxFrames)
|
||||
generates (Error error);
|
||||
|
||||
/**
|
||||
* Collects the results of display content color sampling for display.
|
||||
*
|
||||
* Collection of data can occur whether the sampling is in ENABLE or
|
||||
* DISABLE state.
|
||||
*
|
||||
* @param display is the display to which the sampling is collected.
|
||||
* @param maxFrames is the maximum number of frames that should be represented in the sample.
|
||||
* The sample represents the most-recently posted frames.
|
||||
* If maxFrames is 0, all frames are to be represented by the sample.
|
||||
* @param timestamp is the timestamp after which any frames were posted that should be
|
||||
* included in the sample. Timestamp is CLOCK_MONOTONIC.
|
||||
* If timestamp is 0, do not filter from the sample by time.
|
||||
* @return error is NONE upon success. Otherwise,
|
||||
* BAD_DISPLAY when an invalid display was passed in, or
|
||||
* UNSUPPORTED when there is no efficient way to sample, or
|
||||
* BAD_PARAMETER when the component is not supported by the hardware.
|
||||
* @return frameCount The number of frames represented by this sample.
|
||||
* @return sampleComponent0 is a histogram counting how many times a pixel of a given value
|
||||
* was displayed onscreen for FORMAT_COMPONENT_0.
|
||||
* The buckets of the histogram are evenly weighted, the number of buckets
|
||||
* is device specific.
|
||||
* eg, for RGBA_8888, if sampleComponent0 is {10, 6, 4, 1} this means that
|
||||
* 10 red pixels were displayed onscreen in range 0x00->0x3F, 6 red pixels
|
||||
* were displayed onscreen in range 0x40->0x7F, etc.
|
||||
* @return sampleComponent1 is the same sample definition as sampleComponent0,
|
||||
* but for FORMAT_COMPONENT_1.
|
||||
* @return sampleComponent2 is the same sample definition as sampleComponent0,
|
||||
* but for FORMAT_COMPONENT_2.
|
||||
* @return sampleComponent3 is the same sample definition as sampleComponent0,
|
||||
* but for FORMAT_COMPONENT_3.
|
||||
*/
|
||||
getDisplayedContentSample(Display display, uint64_t maxFrames, uint64_t timestamp)
|
||||
generates (Error error,
|
||||
uint64_t frameCount,
|
||||
vec<uint64_t> sampleComponent0,
|
||||
vec<uint64_t> sampleComponent1,
|
||||
vec<uint64_t> sampleComponent2,
|
||||
vec<uint64_t> sampleComponent3);
|
||||
|
||||
/**
|
||||
* Executes commands from the input command message queue. Return values
|
||||
* generated by the input commands are written to the output command
|
||||
|
|
|
@ -91,6 +91,42 @@ class ComposerClientImpl : public V2_2::hal::detail::ComposerClientImpl<Interfac
|
|||
return Void();
|
||||
}
|
||||
|
||||
Return<void> getDisplayedContentSamplingAttributes(
|
||||
uint64_t display,
|
||||
IComposerClient::getDisplayedContentSamplingAttributes_cb hidl_cb) override {
|
||||
common::V1_1::PixelFormat format;
|
||||
common::V1_2::Dataspace dataspace;
|
||||
hidl_bitfield<IComposerClient::FormatColorComponent> componentMask;
|
||||
Error error =
|
||||
mHal->getDisplayedContentSamplingAttributes(display, format, dataspace, componentMask);
|
||||
hidl_cb(error, format, dataspace, componentMask);
|
||||
return Void();
|
||||
}
|
||||
|
||||
Return<Error> setDisplayedContentSamplingEnabled(
|
||||
uint64_t display, IComposerClient::DisplayedContentSampling enable,
|
||||
hidl_bitfield<IComposerClient::FormatColorComponent> componentMask,
|
||||
uint64_t maxFrames) override {
|
||||
return mHal->setDisplayedContentSamplingEnabled(display, enable, componentMask, maxFrames);
|
||||
}
|
||||
|
||||
Return<void> getDisplayedContentSample(
|
||||
uint64_t display, uint64_t maxFrames, uint64_t timestamp,
|
||||
IComposerClient::getDisplayedContentSample_cb hidl_cb) override {
|
||||
uint64_t frameCount;
|
||||
hidl_vec<uint64_t> sampleComponent0;
|
||||
hidl_vec<uint64_t> sampleComponent1;
|
||||
hidl_vec<uint64_t> sampleComponent2;
|
||||
hidl_vec<uint64_t> sampleComponent3;
|
||||
|
||||
Error error = mHal->getDisplayedContentSample(display, maxFrames, timestamp, frameCount,
|
||||
sampleComponent0, sampleComponent1,
|
||||
sampleComponent2, sampleComponent3);
|
||||
hidl_cb(error, frameCount, sampleComponent0, sampleComponent1, sampleComponent2,
|
||||
sampleComponent3);
|
||||
return Void();
|
||||
}
|
||||
|
||||
Return<void> executeCommands_2_3(uint32_t inLength, const hidl_vec<hidl_handle>& inHandles,
|
||||
IComposerClient::executeCommands_2_2_cb hidl_cb) override {
|
||||
std::lock_guard<std::mutex> lock(mCommandEngineMutex);
|
||||
|
|
|
@ -72,6 +72,18 @@ class ComposerHal : public V2_2::hal::ComposerHal {
|
|||
virtual Error getDisplayIdentificationData(Display display, uint8_t* outPort,
|
||||
std::vector<uint8_t>* outData) = 0;
|
||||
virtual Error setLayerColorTransform(Display display, Layer layer, const float* matrix) = 0;
|
||||
virtual Error getDisplayedContentSamplingAttributes(
|
||||
uint64_t display, PixelFormat& format, Dataspace& dataspace,
|
||||
hidl_bitfield<IComposerClient::FormatColorComponent>& componentMask) = 0;
|
||||
virtual Error setDisplayedContentSamplingEnabled(
|
||||
uint64_t display, IComposerClient::DisplayedContentSampling enable,
|
||||
hidl_bitfield<IComposerClient::FormatColorComponent> componentMask, uint64_t maxFrames) = 0;
|
||||
virtual Error getDisplayedContentSample(uint64_t display, uint64_t maxFrames,
|
||||
uint64_t timestamp, uint64_t& frameCount,
|
||||
hidl_vec<uint64_t>& sampleComponent0,
|
||||
hidl_vec<uint64_t>& sampleComponent1,
|
||||
hidl_vec<uint64_t>& sampleComponent2,
|
||||
hidl_vec<uint64_t>& sampleComponent3) = 0;
|
||||
};
|
||||
|
||||
} // namespace hal
|
||||
|
|
|
@ -104,6 +104,65 @@ class HwcHalImpl : public V2_2::passthrough::detail::HwcHalImpl<Hal> {
|
|||
return static_cast<Error>(err);
|
||||
}
|
||||
|
||||
Error getDisplayedContentSamplingAttributes(
|
||||
uint64_t display, PixelFormat& format, Dataspace& dataspace,
|
||||
hidl_bitfield<IComposerClient::FormatColorComponent>& componentMask) override {
|
||||
if (!mDispatch.getDisplayedContentSamplingAttributes) {
|
||||
return Error::UNSUPPORTED;
|
||||
}
|
||||
int32_t formatRaw = 0;
|
||||
int32_t dataspaceRaw = 0;
|
||||
uint8_t componentMaskRaw = 0;
|
||||
int32_t errorRaw = mDispatch.getDisplayedContentSamplingAttributes(
|
||||
mDevice, display, &formatRaw, &dataspaceRaw, &componentMaskRaw);
|
||||
auto error = static_cast<Error>(errorRaw);
|
||||
if (error == Error::NONE) {
|
||||
format = static_cast<PixelFormat>(formatRaw);
|
||||
dataspace = static_cast<Dataspace>(dataspaceRaw);
|
||||
componentMask =
|
||||
static_cast<hidl_bitfield<IComposerClient::FormatColorComponent>>(componentMaskRaw);
|
||||
}
|
||||
return error;
|
||||
};
|
||||
|
||||
Error setDisplayedContentSamplingEnabled(
|
||||
uint64_t display, IComposerClient::DisplayedContentSampling enable,
|
||||
hidl_bitfield<IComposerClient::FormatColorComponent> componentMask,
|
||||
uint64_t maxFrames) override {
|
||||
if (!mDispatch.setDisplayedContentSamplingEnabled) {
|
||||
return Error::UNSUPPORTED;
|
||||
}
|
||||
return static_cast<Error>(mDispatch.setDisplayedContentSamplingEnabled(
|
||||
mDevice, display, static_cast<int32_t>(enable), componentMask, maxFrames));
|
||||
}
|
||||
|
||||
Error getDisplayedContentSample(uint64_t display, uint64_t maxFrames, uint64_t timestamp,
|
||||
uint64_t& frameCount, hidl_vec<uint64_t>& sampleComponent0,
|
||||
hidl_vec<uint64_t>& sampleComponent1,
|
||||
hidl_vec<uint64_t>& sampleComponent2,
|
||||
hidl_vec<uint64_t>& sampleComponent3) override {
|
||||
if (!mDispatch.getDisplayedContentSample) {
|
||||
return Error::UNSUPPORTED;
|
||||
}
|
||||
|
||||
int32_t size[4] = {0};
|
||||
auto errorRaw = mDispatch.getDisplayedContentSample(mDevice, display, maxFrames, timestamp,
|
||||
&frameCount, size, nullptr);
|
||||
if (errorRaw != HWC2_ERROR_NONE) {
|
||||
return static_cast<Error>(errorRaw);
|
||||
}
|
||||
|
||||
sampleComponent0.resize(size[0]);
|
||||
sampleComponent1.resize(size[1]);
|
||||
sampleComponent2.resize(size[2]);
|
||||
sampleComponent3.resize(size[3]);
|
||||
uint64_t* samples[] = {sampleComponent0.data(), sampleComponent1.data(),
|
||||
sampleComponent2.data(), sampleComponent3.data()};
|
||||
errorRaw = mDispatch.getDisplayedContentSample(mDevice, display, maxFrames, timestamp,
|
||||
&frameCount, size, samples);
|
||||
return static_cast<Error>(errorRaw);
|
||||
}
|
||||
|
||||
protected:
|
||||
bool initDispatch() override {
|
||||
if (!BaseType2_2::initDispatch()) {
|
||||
|
@ -114,6 +173,12 @@ class HwcHalImpl : public V2_2::passthrough::detail::HwcHalImpl<Hal> {
|
|||
&mDispatch.getDisplayIdentificationData);
|
||||
this->initOptionalDispatch(HWC2_FUNCTION_SET_LAYER_COLOR_TRANSFORM,
|
||||
&mDispatch.setLayerColorTransform);
|
||||
this->initOptionalDispatch(HWC2_FUNCTION_GET_DISPLAYED_CONTENT_SAMPLING_ATTRIBUTES,
|
||||
&mDispatch.getDisplayedContentSamplingAttributes);
|
||||
this->initOptionalDispatch(HWC2_FUNCTION_SET_DISPLAYED_CONTENT_SAMPLING_ENABLED,
|
||||
&mDispatch.setDisplayedContentSamplingEnabled);
|
||||
this->initOptionalDispatch(HWC2_FUNCTION_GET_DISPLAYED_CONTENT_SAMPLE,
|
||||
&mDispatch.getDisplayedContentSample);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -121,6 +186,9 @@ class HwcHalImpl : public V2_2::passthrough::detail::HwcHalImpl<Hal> {
|
|||
struct {
|
||||
HWC2_PFN_GET_DISPLAY_IDENTIFICATION_DATA getDisplayIdentificationData;
|
||||
HWC2_PFN_SET_LAYER_COLOR_TRANSFORM setLayerColorTransform;
|
||||
HWC2_PFN_GET_DISPLAYED_CONTENT_SAMPLING_ATTRIBUTES getDisplayedContentSamplingAttributes;
|
||||
HWC2_PFN_SET_DISPLAYED_CONTENT_SAMPLING_ENABLED setDisplayedContentSamplingEnabled;
|
||||
HWC2_PFN_GET_DISPLAYED_CONTENT_SAMPLE getDisplayedContentSample;
|
||||
} mDispatch = {};
|
||||
|
||||
using BaseType2_2 = V2_2::passthrough::detail::HwcHalImpl<Hal>;
|
||||
|
|
|
@ -108,6 +108,48 @@ bool ComposerClient::getClientTargetSupport_2_3(Display display, uint32_t width,
|
|||
return error == Error::NONE;
|
||||
}
|
||||
|
||||
Error ComposerClient::getDisplayedContentSamplingAttributes(
|
||||
uint64_t display, PixelFormat& format, Dataspace& dataspace,
|
||||
hidl_bitfield<IComposerClient::FormatColorComponent>& componentMask) {
|
||||
auto error = Error::BAD_PARAMETER;
|
||||
mClient->getDisplayedContentSamplingAttributes(
|
||||
display, [&](const auto& tmpError, const auto& tmpFormat, const auto& tmpDataspace,
|
||||
const auto& tmpComponentMask) {
|
||||
error = tmpError;
|
||||
format = tmpFormat;
|
||||
dataspace = tmpDataspace;
|
||||
componentMask = tmpComponentMask;
|
||||
});
|
||||
return error;
|
||||
}
|
||||
|
||||
Error ComposerClient::setDisplayedContentSamplingEnabled(
|
||||
uint64_t display, IComposerClient::DisplayedContentSampling enable,
|
||||
hidl_bitfield<IComposerClient::FormatColorComponent> componentMask, uint64_t maxFrames) {
|
||||
return mClient->setDisplayedContentSamplingEnabled(display, enable, componentMask, maxFrames);
|
||||
}
|
||||
|
||||
Error ComposerClient::getDisplayedContentSample(uint64_t display, uint64_t maxFrames,
|
||||
uint64_t timestamp, uint64_t& frameCount,
|
||||
hidl_vec<uint64_t>& sampleComponent0,
|
||||
hidl_vec<uint64_t>& sampleComponent1,
|
||||
hidl_vec<uint64_t>& sampleComponent2,
|
||||
hidl_vec<uint64_t>& sampleComponent3) {
|
||||
auto error = Error::BAD_PARAMETER;
|
||||
mClient->getDisplayedContentSample(
|
||||
display, maxFrames, timestamp,
|
||||
[&](const auto& tmpError, const auto& tmpFrameCount, const auto& tmpSamples0,
|
||||
const auto& tmpSamples1, const auto& tmpSamples2, const auto& tmpSamples3) {
|
||||
error = tmpError;
|
||||
frameCount = tmpFrameCount;
|
||||
sampleComponent0 = tmpSamples0;
|
||||
sampleComponent1 = tmpSamples1;
|
||||
sampleComponent2 = tmpSamples2;
|
||||
sampleComponent3 = tmpSamples3;
|
||||
});
|
||||
return error;
|
||||
}
|
||||
|
||||
} // namespace vts
|
||||
} // namespace V2_3
|
||||
} // namespace composer
|
||||
|
|
|
@ -37,6 +37,7 @@ using common::V1_1::RenderIntent;
|
|||
using common::V1_2::ColorMode;
|
||||
using common::V1_2::Dataspace;
|
||||
using V2_1::Display;
|
||||
using V2_1::Error;
|
||||
using V2_3::IComposer;
|
||||
using V2_3::IComposerClient;
|
||||
|
||||
|
@ -67,6 +68,17 @@ class ComposerClient : public V2_2::vts::ComposerClient {
|
|||
|
||||
bool getDisplayIdentificationData(Display display, uint8_t* outPort,
|
||||
std::vector<uint8_t>* outData);
|
||||
Error getDisplayedContentSamplingAttributes(
|
||||
uint64_t display, PixelFormat& format, Dataspace& dataspace,
|
||||
hidl_bitfield<IComposerClient::FormatColorComponent>& componentMask);
|
||||
Error setDisplayedContentSamplingEnabled(
|
||||
uint64_t display, IComposerClient::DisplayedContentSampling enable,
|
||||
hidl_bitfield<IComposerClient::FormatColorComponent> componentMask, uint64_t maxFrames);
|
||||
Error getDisplayedContentSample(uint64_t display, uint64_t maxFrames, uint64_t timestamp,
|
||||
uint64_t& frameCount, hidl_vec<uint64_t>& sampleComponent0,
|
||||
hidl_vec<uint64_t>& sampleComponent1,
|
||||
hidl_vec<uint64_t>& sampleComponent2,
|
||||
hidl_vec<uint64_t>& sampleComponent3);
|
||||
|
||||
std::vector<ColorMode> getColorModes_2_3(Display display);
|
||||
|
||||
|
|
|
@ -355,6 +355,78 @@ TEST_F(GraphicsComposerHidlTest, SetLayerColorTransform) {
|
|||
execute();
|
||||
}
|
||||
|
||||
TEST_F(GraphicsComposerHidlTest, GetDisplayedContentSamplingAttributes) {
|
||||
using common::V1_1::PixelFormat;
|
||||
using common::V1_2::Dataspace;
|
||||
|
||||
int constexpr invalid = -1;
|
||||
auto format = static_cast<PixelFormat>(invalid);
|
||||
auto dataspace = static_cast<Dataspace>(invalid);
|
||||
auto componentMask = static_cast<hidl_bitfield<IComposerClient::FormatColorComponent>>(invalid);
|
||||
auto error = mComposerClient->getDisplayedContentSamplingAttributes(mPrimaryDisplay, format,
|
||||
dataspace, componentMask);
|
||||
|
||||
if (error == Error::UNSUPPORTED) {
|
||||
SUCCEED() << "Device does not support optional extension. Test skipped";
|
||||
return;
|
||||
}
|
||||
|
||||
EXPECT_EQ(error, Error::NONE);
|
||||
EXPECT_NE(format, static_cast<PixelFormat>(invalid));
|
||||
EXPECT_NE(dataspace, static_cast<Dataspace>(invalid));
|
||||
EXPECT_NE(componentMask,
|
||||
static_cast<hidl_bitfield<IComposerClient::FormatColorComponent>>(invalid));
|
||||
};
|
||||
|
||||
TEST_F(GraphicsComposerHidlTest, SetDisplayedContentSamplingEnabled) {
|
||||
auto const maxFrames = 10;
|
||||
auto const enableAllComponents = 0;
|
||||
auto error = mComposerClient->setDisplayedContentSamplingEnabled(
|
||||
mPrimaryDisplay, IComposerClient::DisplayedContentSampling::ENABLE, enableAllComponents,
|
||||
maxFrames);
|
||||
if (error == Error::UNSUPPORTED) {
|
||||
SUCCEED() << "Device does not support optional extension. Test skipped";
|
||||
return;
|
||||
}
|
||||
EXPECT_EQ(error, Error::NONE);
|
||||
|
||||
error = mComposerClient->setDisplayedContentSamplingEnabled(
|
||||
mPrimaryDisplay, IComposerClient::DisplayedContentSampling::DISABLE, enableAllComponents,
|
||||
maxFrames);
|
||||
EXPECT_EQ(error, Error::NONE);
|
||||
}
|
||||
|
||||
TEST_F(GraphicsComposerHidlTest, GetDisplayedContentSample) {
|
||||
int constexpr invalid = -1;
|
||||
auto format = static_cast<PixelFormat>(invalid);
|
||||
auto dataspace = static_cast<Dataspace>(invalid);
|
||||
auto componentMask = static_cast<hidl_bitfield<IComposerClient::FormatColorComponent>>(invalid);
|
||||
auto error = mComposerClient->getDisplayedContentSamplingAttributes(mPrimaryDisplay, format,
|
||||
dataspace, componentMask);
|
||||
|
||||
uint64_t maxFrames = 10;
|
||||
uint64_t timestamp = 0;
|
||||
uint64_t frameCount = 0;
|
||||
hidl_array<hidl_vec<uint64_t>, 4> histogram;
|
||||
error = mComposerClient->getDisplayedContentSample(mPrimaryDisplay, maxFrames, timestamp,
|
||||
frameCount, histogram[0], histogram[1],
|
||||
histogram[2], histogram[3]);
|
||||
if (error == Error::UNSUPPORTED) {
|
||||
SUCCEED() << "Device does not support optional extension. Test skipped";
|
||||
return;
|
||||
}
|
||||
|
||||
EXPECT_EQ(error, Error::NONE);
|
||||
EXPECT_LE(frameCount, maxFrames);
|
||||
for (auto i = 0; i < histogram.size(); i++) {
|
||||
if (componentMask & (1 << i)) {
|
||||
EXPECT_NE(histogram[i].size(), 0);
|
||||
} else {
|
||||
EXPECT_EQ(histogram[i].size(), 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace vts
|
||||
} // namespace V2_3
|
||||
|
|
Loading…
Reference in a new issue