mirror of
https://github.com/juce-framework/JUCE.git
synced 2026-01-10 23:44:24 +00:00
GL fixes, clean-ups, docs.
This commit is contained in:
parent
87d3acf19f
commit
0f8cc04904
10 changed files with 188 additions and 125 deletions
|
|
@ -60,7 +60,8 @@ public:
|
|||
addAndMakeVisible (&sizeSlider);
|
||||
sizeSlider.setBounds ("parent.width * 0.05, parent.height - 35, parent.width * 0.6, top + 24");
|
||||
|
||||
openGLContext.setRenderer (this, true);
|
||||
openGLContext.setRenderer (this);
|
||||
openGLContext.setComponentPaintingEnabled (true);
|
||||
openGLContext.attachTo (*this);
|
||||
|
||||
startTimer (1000 / 30);
|
||||
|
|
|
|||
|
|
@ -72,7 +72,6 @@ public:
|
|||
}
|
||||
|
||||
bool makeActive() const noexcept { return isInsideGLCallback; }
|
||||
bool makeInactive() const noexcept { return true; }
|
||||
bool isActive() const noexcept { return isInsideGLCallback; }
|
||||
|
||||
void swapBuffers() const noexcept {}
|
||||
|
|
|
|||
|
|
@ -116,11 +116,6 @@ public:
|
|||
return true;
|
||||
}
|
||||
|
||||
bool makeInactive() const noexcept
|
||||
{
|
||||
return (! isActive()) || [EAGLContext setCurrentContext: nil];
|
||||
}
|
||||
|
||||
bool isActive() const noexcept
|
||||
{
|
||||
return [EAGLContext currentContext] == context;
|
||||
|
|
|
|||
|
|
@ -114,7 +114,7 @@ public:
|
|||
|
||||
void shutdownOnRenderThread()
|
||||
{
|
||||
makeInactive();
|
||||
glXMakeCurrent (display, None, 0);
|
||||
glXDestroyContext (display, renderContext);
|
||||
renderContext = nullptr;
|
||||
}
|
||||
|
|
@ -125,11 +125,6 @@ public:
|
|||
&& glXMakeCurrent (display, embeddedWindow, renderContext);
|
||||
}
|
||||
|
||||
bool makeInactive() const noexcept
|
||||
{
|
||||
return (! isActive()) || glXMakeCurrent (display, None, 0);
|
||||
}
|
||||
|
||||
bool isActive() const noexcept
|
||||
{
|
||||
return glXGetCurrentContext() == renderContext && renderContext != 0;
|
||||
|
|
|
|||
|
|
@ -36,7 +36,6 @@
|
|||
|
||||
- (id) initWithFrame: (NSRect) frameRect pixelFormat: (NSOpenGLPixelFormat*) format;
|
||||
- (bool) makeActive;
|
||||
- (void) makeInactive;
|
||||
- (void) reshape;
|
||||
- (void) rightMouseDown: (NSEvent*) ev;
|
||||
- (void) rightMouseUp: (NSEvent*) ev;
|
||||
|
|
@ -84,12 +83,6 @@
|
|||
return true;
|
||||
}
|
||||
|
||||
- (void) makeInactive
|
||||
{
|
||||
const juce::ScopedLock sl (*contextLock);
|
||||
[NSOpenGLContext clearCurrentContext];
|
||||
}
|
||||
|
||||
- (void) _surfaceNeedsUpdate: (NSNotification*) notification
|
||||
{
|
||||
(void) notification;
|
||||
|
|
@ -187,12 +180,6 @@ public:
|
|||
return true;
|
||||
}
|
||||
|
||||
bool makeInactive() const noexcept
|
||||
{
|
||||
[view makeInactive];
|
||||
return true;
|
||||
}
|
||||
|
||||
bool isActive() const noexcept
|
||||
{
|
||||
return [NSOpenGLContext currentContext] == renderContext;
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ public:
|
|||
initialiseGLExtensions();
|
||||
|
||||
const int wglFormat = wglChoosePixelFormatExtension (pixelFormat);
|
||||
makeInactive();
|
||||
wglMakeCurrent (0, 0);
|
||||
|
||||
if (wglFormat != pixFormat && wglFormat != 0)
|
||||
{
|
||||
|
|
@ -94,11 +94,6 @@ public:
|
|||
return wglMakeCurrent (dc, renderContext) != FALSE;
|
||||
}
|
||||
|
||||
bool makeInactive() const noexcept
|
||||
{
|
||||
return (! isActive()) || (wglMakeCurrent (0, 0) != FALSE);
|
||||
}
|
||||
|
||||
bool isActive() const noexcept
|
||||
{
|
||||
return wglGetCurrentContext() == renderContext;
|
||||
|
|
|
|||
|
|
@ -350,11 +350,8 @@ void OpenGLContext::NativeContext::renderCallback()
|
|||
class OpenGLContext::Attachment : public ComponentMovementWatcher
|
||||
{
|
||||
public:
|
||||
Attachment (OpenGLContext& context_, Component& comp,
|
||||
const OpenGLPixelFormat& pixelFormat_,
|
||||
const OpenGLContext* contextToShareWith_)
|
||||
: ComponentMovementWatcher (&comp), context (context_),
|
||||
pixelFormat (pixelFormat_), contextToShareWith (contextToShareWith_)
|
||||
Attachment (OpenGLContext& context_, Component& comp)
|
||||
: ComponentMovementWatcher (&comp), context (context_)
|
||||
{
|
||||
if (canBeAttached (comp))
|
||||
attach();
|
||||
|
|
@ -418,8 +415,6 @@ public:
|
|||
|
||||
private:
|
||||
OpenGLContext& context;
|
||||
OpenGLPixelFormat pixelFormat;
|
||||
const OpenGLContext* contextToShareWith;
|
||||
|
||||
static bool canBeAttached (const Component& comp) noexcept
|
||||
{
|
||||
|
|
@ -435,7 +430,8 @@ private:
|
|||
{
|
||||
Component* const comp = getComponent();
|
||||
comp->setCachedComponentImage (new CachedImage (context, *comp,
|
||||
pixelFormat, contextToShareWith));
|
||||
context.pixelFormat,
|
||||
context.contextToShareWith));
|
||||
}
|
||||
|
||||
void detach()
|
||||
|
|
@ -446,7 +442,7 @@ private:
|
|||
|
||||
//==============================================================================
|
||||
OpenGLContext::OpenGLContext()
|
||||
: nativeContext (nullptr), renderer (nullptr),
|
||||
: nativeContext (nullptr), renderer (nullptr), contextToShareWith (nullptr),
|
||||
width (0), height (0), renderComponents (true)
|
||||
{
|
||||
}
|
||||
|
|
@ -456,37 +452,55 @@ OpenGLContext::~OpenGLContext()
|
|||
detach();
|
||||
}
|
||||
|
||||
void OpenGLContext::setRenderer (OpenGLRenderer* rendererToUse,
|
||||
bool shouldAlsoPaintComponent) noexcept
|
||||
void OpenGLContext::setRenderer (OpenGLRenderer* rendererToUse) noexcept
|
||||
{
|
||||
// This method must not be called when the context has already been attached!
|
||||
// Call it before attaching your context, or use detach() first, before calling this!
|
||||
jassert (nativeContext == nullptr);
|
||||
|
||||
renderer = rendererToUse;
|
||||
renderComponents = shouldAlsoPaintComponent;
|
||||
}
|
||||
|
||||
void OpenGLContext::attachTo (Component& component,
|
||||
const OpenGLPixelFormat& pixelFormat,
|
||||
const OpenGLContext* contextToShareWith)
|
||||
void OpenGLContext::setComponentPaintingEnabled (bool shouldPaintComponent) noexcept
|
||||
{
|
||||
if (getTargetComponent() != &component)
|
||||
{
|
||||
detach();
|
||||
// This method must not be called when the context has already been attached!
|
||||
// Call it before attaching your context, or use detach() first, before calling this!
|
||||
jassert (nativeContext == nullptr);
|
||||
|
||||
width = component.getWidth();
|
||||
height = component.getHeight();
|
||||
renderComponents = shouldPaintComponent;
|
||||
}
|
||||
|
||||
attachment = new Attachment (*this, component,
|
||||
pixelFormat, contextToShareWith);
|
||||
}
|
||||
void OpenGLContext::setPixelFormat (const OpenGLPixelFormat& preferredPixelFormat) noexcept
|
||||
{
|
||||
// This method must not be called when the context has already been attached!
|
||||
// Call it before attaching your context, or use detach() first, before calling this!
|
||||
jassert (nativeContext == nullptr);
|
||||
|
||||
pixelFormat = preferredPixelFormat;
|
||||
}
|
||||
|
||||
void OpenGLContext::setContextToShareWith (const OpenGLContext* context) noexcept
|
||||
{
|
||||
// This method must not be called when the context has already been attached!
|
||||
// Call it before attaching your context, or use detach() first, before calling this!
|
||||
jassert (nativeContext == nullptr);
|
||||
|
||||
contextToShareWith = context;
|
||||
}
|
||||
|
||||
void OpenGLContext::attachTo (Component& component)
|
||||
{
|
||||
component.repaint();
|
||||
attachTo (component, OpenGLPixelFormat(), nullptr);
|
||||
|
||||
if (getTargetComponent() != &component)
|
||||
{
|
||||
detach();
|
||||
|
||||
width = component.getWidth();
|
||||
height = component.getHeight();
|
||||
|
||||
attachment = new Attachment (*this, component);
|
||||
}
|
||||
}
|
||||
|
||||
void OpenGLContext::detach()
|
||||
|
|
@ -522,7 +536,6 @@ OpenGLContext* OpenGLContext::getCurrentContext()
|
|||
}
|
||||
|
||||
bool OpenGLContext::makeActive() const noexcept { return nativeContext != nullptr && nativeContext->makeActive(); }
|
||||
bool OpenGLContext::makeInactive() const noexcept { return nativeContext != nullptr && nativeContext->makeInactive(); }
|
||||
bool OpenGLContext::isActive() const noexcept { return nativeContext != nullptr && nativeContext->isActive(); }
|
||||
|
||||
void OpenGLContext::triggerRepaint()
|
||||
|
|
@ -575,7 +588,7 @@ bool OpenGLContext::areShadersAvailable() const
|
|||
return c != nullptr && c->shadersAvailable;
|
||||
}
|
||||
|
||||
ReferenceCountedObjectPtr<ReferenceCountedObject> OpenGLContext::getAssociatedObject (const char* name) const
|
||||
ReferenceCountedObject* OpenGLContext::getAssociatedObject (const char* name) const
|
||||
{
|
||||
jassert (name != nullptr);
|
||||
|
||||
|
|
@ -635,7 +648,7 @@ void OpenGLContext::copyTexture (const Rectangle<int>& targetClipArea,
|
|||
static const OverlayShaderProgram& select (OpenGLContext& context)
|
||||
{
|
||||
static const char programValueID[] = "juceGLComponentOverlayShader";
|
||||
OverlayShaderProgram* program = static_cast <OverlayShaderProgram*> (context.getAssociatedObject (programValueID).getObject());
|
||||
OverlayShaderProgram* program = static_cast <OverlayShaderProgram*> (context.getAssociatedObject (programValueID));
|
||||
|
||||
if (program == nullptr)
|
||||
{
|
||||
|
|
@ -716,7 +729,9 @@ void OpenGLContext::copyTexture (const Rectangle<int>& targetClipArea,
|
|||
JUCE_CHECK_OPENGL_ERROR
|
||||
|
||||
glDrawArrays (GL_TRIANGLE_STRIP, 0, 4);
|
||||
|
||||
extensions.glUseProgram (0);
|
||||
extensions.glDisableVertexAttribArray (program.params.positionAttribute.attributeID);
|
||||
}
|
||||
#if JUCE_USE_OPENGL_FIXED_FUNCTION
|
||||
else
|
||||
|
|
|
|||
|
|
@ -31,6 +31,10 @@
|
|||
|
||||
//==============================================================================
|
||||
/**
|
||||
A base class that should be implemented by classes which want to render openGL
|
||||
on a background thread.
|
||||
|
||||
@see OpenGLContext
|
||||
*/
|
||||
class JUCE_API OpenGLRenderer
|
||||
{
|
||||
|
|
@ -38,24 +42,42 @@ public:
|
|||
OpenGLRenderer() {}
|
||||
virtual ~OpenGLRenderer() {}
|
||||
|
||||
/**
|
||||
/** Called when a new GL context has been created.
|
||||
You can use this as an opportunity to create your textures, shaders, etc.
|
||||
When the method is invoked, the new GL context will be active.
|
||||
Note that this callback will be made on a background thread, so make sure
|
||||
that your implementation is thread-safe.
|
||||
*/
|
||||
virtual void newOpenGLContextCreated() = 0;
|
||||
|
||||
/**
|
||||
/** Called when you should render the next openGL frame.
|
||||
Note that this callback will be made on a background thread, so make sure
|
||||
that your implementation is thread-safe.
|
||||
*/
|
||||
virtual void renderOpenGL() = 0;
|
||||
|
||||
/**
|
||||
/** Called when the current openGL context is about to close.
|
||||
You can use this opportunity to release any GL resources that you may have
|
||||
created.
|
||||
|
||||
Note that this callback will be made on a background thread, so make sure
|
||||
that your implementation is thread-safe.
|
||||
|
||||
(Also note that on Android, this callback won't happen, because there's currently
|
||||
no way to implement it..)
|
||||
*/
|
||||
virtual void openGLContextClosing() = 0;
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
A base class for types of OpenGL context.
|
||||
Creates an OpenGL context, which can be attached to a component.
|
||||
|
||||
An OpenGLComponent will supply its own context for drawing in its window.
|
||||
To render some OpenGL in a component, you should create an instance of an OpenGLContext,
|
||||
and call attachTo() to make it use your component as its render target.
|
||||
To free the context, either call detach(), or delete the OpenGLContext.
|
||||
|
||||
@see OpenGLRenderer
|
||||
*/
|
||||
class JUCE_API OpenGLContext
|
||||
{
|
||||
|
|
@ -66,30 +88,66 @@ public:
|
|||
~OpenGLContext();
|
||||
|
||||
//==============================================================================
|
||||
/** */
|
||||
void setRenderer (OpenGLRenderer* rendererToUse,
|
||||
bool shouldAlsoPaintComponent) noexcept;
|
||||
/** Gives the context an OpenGLRenderer to use to do the drawing.
|
||||
The object that you give it will not be owned by the context, so it's the caller's
|
||||
responsibility to manage its lifetime and make sure that it doesn't get deleted
|
||||
while the context may be using it. To stop the context using a renderer, just call
|
||||
this method with a null pointer.
|
||||
Note: This must be called BEFORE attaching your context to a target component!
|
||||
*/
|
||||
void setRenderer (OpenGLRenderer* rendererToUse) noexcept;
|
||||
|
||||
/** */
|
||||
void attachTo (Component& component);
|
||||
/** Enables or disables the use of the GL context to perform 2D rendering
|
||||
of the component to which it is attached.
|
||||
If this is false, then only your OpenGLRenderer will be used to perform
|
||||
any rendering. If true, then each time your target's paint() method needs
|
||||
to be called, an OpenGLGraphicsContext will be used to render it, (after
|
||||
calling your OpenGLRenderer if there is one).
|
||||
|
||||
/** */
|
||||
void attachTo (Component& component,
|
||||
const OpenGLPixelFormat& preferredPixelFormat,
|
||||
const OpenGLContext* contextToShareWith);
|
||||
/** */
|
||||
void detach();
|
||||
By default this is set to true. If you're not using any paint() method functionality
|
||||
and are doing all your rendering in an OpenGLRenderer, you should disable it
|
||||
to improve performance.
|
||||
|
||||
/** */
|
||||
bool isAttached() const noexcept;
|
||||
Note: This must be called BEFORE attaching your context to a target component!
|
||||
*/
|
||||
void setComponentPaintingEnabled (bool shouldPaintComponent) noexcept;
|
||||
|
||||
/** Sets the pixel format which you'd like to use for the target GL surface.
|
||||
Note: This must be called BEFORE attaching your context to a target component!
|
||||
*/
|
||||
void setPixelFormat (const OpenGLPixelFormat& preferredPixelFormat) noexcept;
|
||||
|
||||
/** Provides a context with which you'd like this context's resources to be shared.
|
||||
The object passed-in here must not be deleted while the context may still be
|
||||
using it! To turn off sharing, you can call this method with a null pointer.
|
||||
Note: This must be called BEFORE attaching your context to a target component!
|
||||
*/
|
||||
void setContextToShareWith (const OpenGLContext* contextToShareWith) noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Makes this context the currently active one. */
|
||||
bool makeActive() const noexcept;
|
||||
/** If this context is currently active, it is disactivated. */
|
||||
bool makeInactive() const noexcept;
|
||||
/** Returns true if this context is currently active. */
|
||||
bool isActive() const noexcept;
|
||||
/** Attaches the context to a target component.
|
||||
|
||||
If the component is not fully visible, this call will wait until the component
|
||||
is shown before actually creating a native context for it.
|
||||
|
||||
When a native context is created, a thread is started, and will be used to call
|
||||
the OpenGLRenderer methods. The context will be floated above the target component,
|
||||
and when the target moves, it will track it. If the component is hidden/shown, the
|
||||
context may be deleted and re-created.
|
||||
*/
|
||||
void attachTo (Component& component);
|
||||
|
||||
/** Detaches the context from its target component and deletes any native resources.
|
||||
If the context has not been attached, this will do nothing. Otherwise, it will block
|
||||
until the context and its thread have been cleaned up.
|
||||
*/
|
||||
void detach();
|
||||
|
||||
/** Returns true if the context is attached to a component and is on-screen.
|
||||
Note that if you call attachTo() for a non-visible component, this method will
|
||||
return false until the component is made visible.
|
||||
*/
|
||||
bool isAttached() const noexcept;
|
||||
|
||||
/** Returns the component to which this context is currently attached, or nullptr. */
|
||||
Component* getTargetComponent() const noexcept;
|
||||
|
|
@ -99,6 +157,56 @@ public:
|
|||
*/
|
||||
static OpenGLContext* getCurrentContext();
|
||||
|
||||
/** Asynchronously causes a repaint to be made. */
|
||||
void triggerRepaint();
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the width of this context */
|
||||
inline int getWidth() const noexcept { return width; }
|
||||
|
||||
/** Returns the height of this context */
|
||||
inline int getHeight() const noexcept { return height; }
|
||||
|
||||
/** If this context is backed by a frame buffer, this returns its ID number,
|
||||
or 0 if the context does not use a framebuffer.
|
||||
*/
|
||||
unsigned int getFrameBufferID() const noexcept;
|
||||
|
||||
/** Returns true if shaders can be used in this context. */
|
||||
bool areShadersAvailable() const;
|
||||
|
||||
/** This structure holds a set of dynamically loaded GL functions for use on this context. */
|
||||
OpenGLExtensionFunctions extensions;
|
||||
|
||||
//==============================================================================
|
||||
/** This retrieves an object that was previously stored with setAssociatedObject().
|
||||
If no object is found with the given name, this will return nullptr.
|
||||
This method must only be called from within the GL rendering methods.
|
||||
@see setAssociatedObject
|
||||
*/
|
||||
ReferenceCountedObject* getAssociatedObject (const char* name) const;
|
||||
|
||||
/** Attaches a named object to the context, which will be deleted when the context is
|
||||
destroyed.
|
||||
|
||||
This allows you to store an object which will be released before the context is
|
||||
deleted. The main purpose is for caching GL objects such as shader programs, which
|
||||
will become invalid when the context is deleted.
|
||||
|
||||
This method must only be called from within the GL rendering methods.
|
||||
*/
|
||||
void setAssociatedObject (const char* name, ReferenceCountedObject* newObject);
|
||||
|
||||
//==============================================================================
|
||||
/** Makes this context the currently active one.
|
||||
You should never need to call this in normal use - the context will already be
|
||||
active when OpenGLRenderer::renderOpenGL() is invoked.
|
||||
*/
|
||||
bool makeActive() const noexcept;
|
||||
|
||||
/** Returns true if this context is currently active for the calling thread. */
|
||||
bool isActive() const noexcept;
|
||||
|
||||
//==============================================================================
|
||||
/** Swaps the buffers (if the context can do this).
|
||||
There's normally no need to call this directly - the buffers will be swapped
|
||||
|
|
@ -122,45 +230,6 @@ public:
|
|||
*/
|
||||
int getSwapInterval() const;
|
||||
|
||||
/** */
|
||||
void triggerRepaint();
|
||||
|
||||
//==============================================================================
|
||||
/** Returns the width of this context */
|
||||
inline int getWidth() const noexcept { return width; }
|
||||
|
||||
/** Returns the height of this context */
|
||||
inline int getHeight() const noexcept { return height; }
|
||||
|
||||
/** If this context is backed by a frame buffer, this returns its ID number,
|
||||
or 0 if the context has no accessible framebuffer.
|
||||
*/
|
||||
unsigned int getFrameBufferID() const noexcept;
|
||||
|
||||
/** Returns true if shaders can be used in this context. */
|
||||
bool areShadersAvailable() const;
|
||||
|
||||
/** This structure holds a set of dynamically loaded GL functions for use on this context. */
|
||||
OpenGLExtensionFunctions extensions;
|
||||
|
||||
/** This retrieves an object that was previously stored with setAssociatedObject().
|
||||
If no object is found with the given name, this will return nullptr.
|
||||
This method must only be called from within the GL rendering methods.
|
||||
@see setAssociatedObject
|
||||
*/
|
||||
ReferenceCountedObjectPtr<ReferenceCountedObject> getAssociatedObject (const char* name) const;
|
||||
|
||||
/** Attaches a named object to the context, which will be deleted when the context is
|
||||
destroyed.
|
||||
|
||||
This allows you to store an object which will be released before the context is
|
||||
deleted. The main purpose is for caching GL objects such as shader programs, which
|
||||
will become invalid when the context is deleted.
|
||||
|
||||
This method must only be called from within the GL rendering methods.
|
||||
*/
|
||||
void setAssociatedObject (const char* name, ReferenceCountedObject* newObject);
|
||||
|
||||
//==============================================================================
|
||||
/** Returns an OS-dependent handle to some kind of underlting OS-provided GL context.
|
||||
|
||||
|
|
@ -197,6 +266,8 @@ private:
|
|||
NativeContext* nativeContext;
|
||||
OpenGLRenderer* renderer;
|
||||
ScopedPointer<Attachment> attachment;
|
||||
OpenGLPixelFormat pixelFormat;
|
||||
const OpenGLContext* contextToShareWith;
|
||||
int width, height;
|
||||
bool renderComponents;
|
||||
|
||||
|
|
|
|||
|
|
@ -1021,7 +1021,7 @@ struct StateHelpers
|
|||
GLuint colour;
|
||||
};
|
||||
|
||||
#if ! JUCE_MAC
|
||||
#if ! (JUCE_MAC || JUCE_ANDROID || JUCE_IOS)
|
||||
enum { numQuads = 64 }; // (had problems with my drivers segfaulting when these buffers are any larger)
|
||||
#else
|
||||
enum { numQuads = 8192 };
|
||||
|
|
@ -1051,7 +1051,7 @@ struct StateHelpers
|
|||
activeShader (nullptr)
|
||||
{
|
||||
const char programValueID[] = "GraphicsContextPrograms";
|
||||
programs = static_cast <ShaderPrograms*> (context.getAssociatedObject (programValueID).getObject());
|
||||
programs = static_cast <ShaderPrograms*> (context.getAssociatedObject (programValueID));
|
||||
|
||||
if (programs == nullptr)
|
||||
{
|
||||
|
|
@ -1154,6 +1154,9 @@ public:
|
|||
#if defined (GL_INDEX_ARRAY)
|
||||
glDisableClientState (GL_INDEX_ARRAY);
|
||||
#endif
|
||||
|
||||
target.context.extensions.glBindBuffer (GL_ARRAY_BUFFER, 0);
|
||||
target.context.extensions.glBindBuffer (GL_ELEMENT_ARRAY_BUFFER, 0);
|
||||
}
|
||||
|
||||
void flush()
|
||||
|
|
|
|||
|
|
@ -142,7 +142,9 @@ void OpenGLTexture::release()
|
|||
{
|
||||
if (textureID != 0)
|
||||
{
|
||||
glDeleteTextures (1, &textureID);
|
||||
if (OpenGLHelpers::isContextActive())
|
||||
glDeleteTextures (1, &textureID);
|
||||
|
||||
textureID = 0;
|
||||
width = 0;
|
||||
height = 0;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue