mirror of
https://github.com/Ryujinx/Opentk.git
synced 2024-12-23 11:35:39 +00:00
Prioritize accelerated formats first
Instead of creating a list of all available formats and iterating through that, we let the driver decide which is the best accelerated format to use for the user parameters. If no such format exists, we fall back to generic acceleration or software acceleration, in turn. This affects issue #21
This commit is contained in:
parent
a7ae4bb038
commit
1723be8a8b
|
@ -37,27 +37,28 @@ namespace OpenTK.Platform.Windows
|
|||
{
|
||||
class WinGraphicsMode : IGraphicsMode
|
||||
{
|
||||
#region Fields
|
||||
enum AccelerationType
|
||||
{
|
||||
// Software acceleration
|
||||
None = 0,
|
||||
// Partial acceleration (Direct3D emulation)
|
||||
MCD,
|
||||
// Full acceleration
|
||||
ICD,
|
||||
}
|
||||
|
||||
readonly List<GraphicsMode> modes = new List<GraphicsMode>();
|
||||
static readonly object SyncRoot = new object();
|
||||
|
||||
#endregion
|
||||
readonly IntPtr Device;
|
||||
readonly List<GraphicsMode> modes = new List<GraphicsMode>();
|
||||
|
||||
#region Constructors
|
||||
|
||||
public WinGraphicsMode(IntPtr device)
|
||||
{
|
||||
lock (SyncRoot)
|
||||
{
|
||||
modes.AddRange(GetModesARB(device));
|
||||
if (modes.Count == 0)
|
||||
modes.AddRange(GetModesPFD(device));
|
||||
if (modes.Count == 0)
|
||||
throw new GraphicsModeException(
|
||||
"No GraphicsMode available. This should never happen, please report a bug at http://www.opentk.com");
|
||||
modes.Sort(new GraphicsModeComparer());
|
||||
}
|
||||
if (device == IntPtr.Zero)
|
||||
throw new ArgumentException();
|
||||
|
||||
Device = device;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
@ -67,57 +68,172 @@ namespace OpenTK.Platform.Windows
|
|||
public GraphicsMode SelectGraphicsMode(ColorFormat color, int depth, int stencil, int samples,
|
||||
ColorFormat accum, int buffers, bool stereo)
|
||||
{
|
||||
GraphicsMode mode = null;
|
||||
do
|
||||
GraphicsMode mode = new GraphicsMode(color, depth, stencil, samples,accum, buffers, stereo);
|
||||
GraphicsMode created_mode = ChoosePixelFormatARB(Device, mode);
|
||||
|
||||
// If ChoosePixelFormatARB failed, iterate through all acceleration types in turn (ICD, MCD, None)
|
||||
// This should fix issue #2224, which causes OpenTK to fail on VMs without hardware acceleration.
|
||||
created_mode = created_mode ?? ChoosePixelFormatPFD(Device, mode, AccelerationType.ICD);
|
||||
created_mode = created_mode ?? ChoosePixelFormatPFD(Device, mode, AccelerationType.MCD);
|
||||
created_mode = created_mode ?? ChoosePixelFormatPFD(Device, mode, AccelerationType.None);
|
||||
|
||||
if (created_mode == null)
|
||||
{
|
||||
mode = modes.Find(delegate(GraphicsMode current)
|
||||
{
|
||||
return ModeSelector(current, color, depth, stencil, samples, accum, buffers, stereo);
|
||||
});
|
||||
} while (mode == null && RelaxParameters(
|
||||
ref color, ref depth, ref stencil, ref samples, ref accum, ref buffers, ref stereo));
|
||||
throw new GraphicsModeException("The requested GraphicsMode is not supported");
|
||||
}
|
||||
|
||||
if (mode == null)
|
||||
mode = modes[0];
|
||||
|
||||
return mode;
|
||||
}
|
||||
|
||||
bool RelaxParameters(ref ColorFormat color, ref int depth, ref int stencil, ref int samples,
|
||||
ref ColorFormat accum, ref int buffers, ref bool stereo)
|
||||
{
|
||||
if (stereo) { stereo = false; return true; }
|
||||
if (buffers != 2) { buffers = 2; return true; }
|
||||
if (accum != 0) { accum = 0; return true; }
|
||||
if (samples != 0) { samples = 0; return true; }
|
||||
if (depth < 16) { depth = 16; return true; }
|
||||
if (depth != 24) { depth = 24; return true; }
|
||||
if (stencil > 0 && stencil != 8) { stencil = 8; return true; }
|
||||
if (stencil == 8) { stencil = 0; return true; }
|
||||
if (color < 8) { color = 8; return true; }
|
||||
if (color < 16) { color = 16; return true; }
|
||||
if (color < 24) { color = 24; return true; }
|
||||
if (color < 32 || color > 32) { color = 32; return true; }
|
||||
return false; // We tried everything we could, no match found.
|
||||
return created_mode;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Private Methods
|
||||
|
||||
#region GetModesPFD
|
||||
#region ChoosePixelFormatARB
|
||||
|
||||
IEnumerable<GraphicsMode> GetModesPFD(IntPtr device)
|
||||
// Queries pixel formats through the WGL_ARB_pixel_format extension
|
||||
// This method only returns accelerated formats. If no format offers
|
||||
// hardware acceleration (e.g. we are running in a VM or in a remote desktop
|
||||
// connection), this method will return 0 formats and we will fall back to
|
||||
// ChoosePixelFormatPFD.
|
||||
GraphicsMode ChoosePixelFormatARB(IntPtr device, GraphicsMode mode)
|
||||
{
|
||||
Debug.WriteLine(String.Format("Device context: {0}", device));
|
||||
GraphicsMode created_mode = null;
|
||||
if (Wgl.Delegates.wglChoosePixelFormatARB != null)
|
||||
{
|
||||
List<int> attributes = new List<int>();
|
||||
attributes.Add((int)WGL_ARB_pixel_format.AccelerationArb);
|
||||
attributes.Add((int)WGL_ARB_pixel_format.FullAccelerationArb);
|
||||
|
||||
Debug.WriteLine("Retrieving PFD pixel formats... ");
|
||||
if (mode.ColorFormat.BitsPerPixel > 0)
|
||||
{
|
||||
attributes.Add((int)WGL_ARB_pixel_format.RedBitsArb);
|
||||
attributes.Add(mode.ColorFormat.Red);
|
||||
attributes.Add((int)WGL_ARB_pixel_format.GreenBitsArb);
|
||||
attributes.Add(mode.ColorFormat.Green);
|
||||
attributes.Add((int)WGL_ARB_pixel_format.BlueBitsArb);
|
||||
attributes.Add(mode.ColorFormat.Blue);
|
||||
attributes.Add((int)WGL_ARB_pixel_format.AlphaBitsArb);
|
||||
attributes.Add(mode.ColorFormat.Alpha);
|
||||
attributes.Add((int)WGL_ARB_pixel_format.ColorBitsArb);
|
||||
attributes.Add(mode.ColorFormat.BitsPerPixel);
|
||||
}
|
||||
|
||||
if (mode.Depth > 0)
|
||||
{
|
||||
attributes.Add((int)WGL_ARB_pixel_format.DepthBitsArb);
|
||||
attributes.Add(mode.Depth);
|
||||
}
|
||||
|
||||
if (mode.Stencil > 0)
|
||||
{
|
||||
attributes.Add((int)WGL_ARB_pixel_format.StencilBitsArb);
|
||||
attributes.Add(mode.Stencil);
|
||||
}
|
||||
|
||||
if (mode.AccumulatorFormat.BitsPerPixel > 0)
|
||||
{
|
||||
attributes.Add((int)WGL_ARB_pixel_format.AccumRedBitsArb);
|
||||
attributes.Add(mode.AccumulatorFormat.Red);
|
||||
attributes.Add((int)WGL_ARB_pixel_format.AccumGreenBitsArb);
|
||||
attributes.Add(mode.AccumulatorFormat.Green);
|
||||
attributes.Add((int)WGL_ARB_pixel_format.AccumBlueBitsArb);
|
||||
attributes.Add(mode.AccumulatorFormat.Blue);
|
||||
attributes.Add((int)WGL_ARB_pixel_format.AccumAlphaBitsArb);
|
||||
attributes.Add(mode.AccumulatorFormat.Alpha);
|
||||
attributes.Add((int)WGL_ARB_pixel_format.AccumBitsArb);
|
||||
attributes.Add(mode.AccumulatorFormat.BitsPerPixel);
|
||||
}
|
||||
|
||||
if (mode.Samples > 0)
|
||||
{
|
||||
attributes.Add((int)WGL_ARB_multisample.SampleBuffersArb);
|
||||
attributes.Add(1);
|
||||
attributes.Add((int)WGL_ARB_multisample.SamplesArb);
|
||||
attributes.Add(mode.Samples);
|
||||
}
|
||||
|
||||
if (mode.Buffers > 0)
|
||||
{
|
||||
attributes.Add((int)WGL_ARB_pixel_format.DoubleBufferArb);
|
||||
attributes.Add(mode.Buffers);
|
||||
}
|
||||
|
||||
if (mode.Stereo)
|
||||
{
|
||||
attributes.Add((int)WGL_ARB_pixel_format.StereoArb);
|
||||
attributes.Add(1);
|
||||
}
|
||||
|
||||
attributes.Add(0);
|
||||
attributes.Add(0);
|
||||
|
||||
int[] format = new int[1];
|
||||
int count;
|
||||
if (Wgl.Arb.ChoosePixelFormat(device, attributes.ToArray(), null, format.Length, format, out count))
|
||||
{
|
||||
created_mode = DescribePixelFormatARB(device, format[0]);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Print("[WGL] ChoosePixelFormatARB failed with {0}", Marshal.GetLastWin32Error());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Print("[WGL] ChoosePixelFormatARB not supported");
|
||||
}
|
||||
|
||||
return created_mode;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ChoosePixelFormatPFD
|
||||
|
||||
GraphicsMode ChoosePixelFormatPFD(IntPtr Device, GraphicsMode mode, AccelerationType requested_acceleration_type)
|
||||
{
|
||||
PixelFormatDescriptor pfd = new PixelFormatDescriptor();
|
||||
pfd.Size = API.PixelFormatDescriptorSize;
|
||||
pfd.Version = API.PixelFormatDescriptorVersion;
|
||||
pfd.Flags =
|
||||
PixelFormatDescriptorFlags.SUPPORT_OPENGL |
|
||||
PixelFormatDescriptorFlags.DRAW_TO_WINDOW;
|
||||
pfd.Size = (short)BlittableValueType<PixelFormatDescriptor>.Stride;
|
||||
|
||||
if (mode.ColorFormat.BitsPerPixel > 0)
|
||||
{
|
||||
pfd.RedBits = (byte)mode.ColorFormat.Red;
|
||||
pfd.GreenBits = (byte)mode.ColorFormat.Green;
|
||||
pfd.BlueBits = (byte)mode.ColorFormat.Blue;
|
||||
pfd.AlphaBits = (byte)mode.ColorFormat.Alpha;
|
||||
pfd.ColorBits = (byte)mode.ColorFormat.BitsPerPixel;
|
||||
}
|
||||
|
||||
if (mode.Depth > 0)
|
||||
{
|
||||
pfd.DepthBits = (byte)mode.Depth;
|
||||
}
|
||||
|
||||
if (mode.Stencil > 0)
|
||||
{
|
||||
pfd.StencilBits = (byte)mode.Stencil;
|
||||
}
|
||||
|
||||
if (mode.AccumulatorFormat.BitsPerPixel > 0)
|
||||
{
|
||||
pfd.AccumRedBits = (byte)mode.AccumulatorFormat.Red;
|
||||
pfd.AccumGreenBits = (byte)mode.AccumulatorFormat.Green;
|
||||
pfd.AccumBlueBits = (byte)mode.AccumulatorFormat.Blue;
|
||||
pfd.AccumAlphaBits = (byte)mode.AccumulatorFormat.Alpha;
|
||||
pfd.AccumBits = (byte)mode.AccumulatorFormat.BitsPerPixel;
|
||||
}
|
||||
|
||||
if (mode.Buffers > 0)
|
||||
{
|
||||
pfd.Flags |= PixelFormatDescriptorFlags.DOUBLEBUFFER;
|
||||
}
|
||||
|
||||
if (mode.Stereo)
|
||||
{
|
||||
pfd.Flags |= PixelFormatDescriptorFlags.DRAW_TO_WINDOW;
|
||||
pfd.Flags |= PixelFormatDescriptorFlags.SUPPORT_OPENGL;
|
||||
}
|
||||
|
||||
// Make sure we don't turn off Aero on Vista and newer.
|
||||
if (Environment.OSVersion.Version.Major >= 6)
|
||||
|
@ -125,109 +241,102 @@ namespace OpenTK.Platform.Windows
|
|||
pfd.Flags |= PixelFormatDescriptorFlags.SUPPORT_COMPOSITION;
|
||||
}
|
||||
|
||||
foreach (bool generic_allowed in new bool[] { false, true })
|
||||
GraphicsMode created_mode = null;
|
||||
int pixelformat = Functions.ChoosePixelFormat(Device, ref pfd);
|
||||
if (pixelformat > 0)
|
||||
{
|
||||
// Iterate through all accelerated formats first. Afterwards, iterate through non-accelerated formats.
|
||||
// This should fix issue #2224, which causes OpenTK to fail on VMs without hardware acceleration.
|
||||
// Note: DescribePixelFormat found in gdi32 is extremely slow on nvidia, for some reason.
|
||||
int pixel = 0;
|
||||
while (Functions.DescribePixelFormat(device, ++pixel, API.PixelFormatDescriptorSize, ref pfd) != 0)
|
||||
AccelerationType acceleration_type = AccelerationType.ICD;
|
||||
if ((pfd.Flags & PixelFormatDescriptorFlags.GENERIC_FORMAT) != 0)
|
||||
{
|
||||
// Ignore non-accelerated formats.
|
||||
if (!generic_allowed && (pfd.Flags & PixelFormatDescriptorFlags.GENERIC_FORMAT) != 0)
|
||||
continue;
|
||||
if ((pfd.Flags & PixelFormatDescriptorFlags.GENERIC_ACCELERATED) != 0)
|
||||
{
|
||||
acceleration_type = AccelerationType.MCD;
|
||||
}
|
||||
else
|
||||
{
|
||||
acceleration_type = AccelerationType.None;
|
||||
}
|
||||
}
|
||||
|
||||
GraphicsMode fmt = new GraphicsMode((IntPtr)pixel,
|
||||
new ColorFormat(pfd.RedBits, pfd.GreenBits, pfd.BlueBits, pfd.AlphaBits),
|
||||
pfd.DepthBits,
|
||||
pfd.StencilBits,
|
||||
0,
|
||||
new ColorFormat(pfd.AccumBits),
|
||||
(pfd.Flags & PixelFormatDescriptorFlags.DOUBLEBUFFER) != 0 ? 2 : 1,
|
||||
(pfd.Flags & PixelFormatDescriptorFlags.STEREO) != 0);
|
||||
|
||||
yield return fmt;
|
||||
if (acceleration_type == requested_acceleration_type)
|
||||
{
|
||||
created_mode = DescribePixelFormatPFD(ref pfd, pixelformat);
|
||||
}
|
||||
}
|
||||
return created_mode;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region GetModesARB
|
||||
#region DescribePixelFormatPFD
|
||||
|
||||
// Queries pixel formats through the WGL_ARB_pixel_format extension
|
||||
// This method only returns accelerated formats. If no format offers
|
||||
// hardware acceleration (e.g. we are running in a VM or in a remote desktop
|
||||
// connection), this method will return 0 formats and we will fall back to
|
||||
// GetModesPFD.
|
||||
IEnumerable<GraphicsMode> GetModesARB(IntPtr device)
|
||||
static GraphicsMode DescribePixelFormatPFD(ref PixelFormatDescriptor pfd, int pixelformat)
|
||||
{
|
||||
// See http://www.opengl.org/registry/specs/ARB/wgl_pixel_format.txt
|
||||
// for more details
|
||||
Debug.Write("Retrieving ARB pixel formats.... ");
|
||||
if (Wgl.Delegates.wglChoosePixelFormatARB == null || Wgl.Delegates.wglGetPixelFormatAttribivARB == null)
|
||||
return new GraphicsMode(
|
||||
new IntPtr(pixelformat),
|
||||
new ColorFormat(pfd.RedBits, pfd.GreenBits, pfd.BlueBits, pfd.AlphaBits),
|
||||
pfd.DepthBits,
|
||||
pfd.StencilBits,
|
||||
0, // MSAA not supported
|
||||
new ColorFormat(pfd.AccumRedBits, pfd.AccumGreenBits, pfd.AccumBlueBits, pfd.AccumAlphaBits),
|
||||
(pfd.Flags & PixelFormatDescriptorFlags.DOUBLEBUFFER) != 0 ? 2 : 1,
|
||||
(pfd.Flags & PixelFormatDescriptorFlags.STEREO) != 0);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region DescribePixelFormatARB
|
||||
|
||||
GraphicsMode DescribePixelFormatARB(IntPtr device, int pixelformat)
|
||||
{
|
||||
GraphicsMode created_mode = null;
|
||||
// See http://www.opengl.org/registry/specs/ARB/wgl_pixel_format.txt for more details
|
||||
if (Wgl.Delegates.wglGetPixelFormatAttribivARB != null)
|
||||
{
|
||||
Debug.WriteLine("failed.");
|
||||
yield break;
|
||||
}
|
||||
|
||||
// Define the list of attributes we are interested in.
|
||||
// We will use each available pixel format for these
|
||||
// attributes using GetPixelFormatAttrib.
|
||||
// The results will be stored in the 'values' array below.
|
||||
int[] attribs = new int[]
|
||||
{
|
||||
(int)WGL_ARB_pixel_format.AccelerationArb,
|
||||
|
||||
(int)WGL_ARB_pixel_format.RedBitsArb,
|
||||
(int)WGL_ARB_pixel_format.GreenBitsArb,
|
||||
(int)WGL_ARB_pixel_format.BlueBitsArb,
|
||||
(int)WGL_ARB_pixel_format.AlphaBitsArb,
|
||||
(int)WGL_ARB_pixel_format.ColorBitsArb,
|
||||
|
||||
(int)WGL_ARB_pixel_format.DepthBitsArb,
|
||||
(int)WGL_ARB_pixel_format.StencilBitsArb,
|
||||
|
||||
(int)WGL_ARB_multisample.SampleBuffersArb,
|
||||
(int)WGL_ARB_multisample.SamplesArb,
|
||||
|
||||
(int)WGL_ARB_pixel_format.AccumRedBitsArb,
|
||||
(int)WGL_ARB_pixel_format.AccumGreenBitsArb,
|
||||
(int)WGL_ARB_pixel_format.AccumBlueBitsArb,
|
||||
(int)WGL_ARB_pixel_format.AccumAlphaBitsArb,
|
||||
(int)WGL_ARB_pixel_format.AccumBitsArb,
|
||||
|
||||
(int)WGL_ARB_pixel_format.DoubleBufferArb,
|
||||
(int)WGL_ARB_pixel_format.StereoArb,
|
||||
0
|
||||
};
|
||||
|
||||
// Allocate storage for the results of GetPixelFormatAttrib queries
|
||||
int[] values = new int[attribs.Length];
|
||||
|
||||
// Get the number of available formats
|
||||
int num_formats;
|
||||
int num_formats_attrib = (int)WGL_ARB_pixel_format.NumberPixelFormatsArb;
|
||||
if (Wgl.Arb.GetPixelFormatAttrib(device, 0, 0, 1, ref num_formats_attrib, out num_formats))
|
||||
{
|
||||
for (int p = 1; p < num_formats; p++)
|
||||
// Define the list of attributes we are interested in.
|
||||
// The results will be stored in the 'values' array below.
|
||||
int[] attribs = new int[]
|
||||
{
|
||||
// Get the format attributes for this pixel format
|
||||
if (!Wgl.Arb.GetPixelFormatAttrib(device, p, 0, attribs.Length - 1, attribs, values))
|
||||
{
|
||||
Debug.Print("[Warning] Failed to detect attributes for PixelFormat:{0}.", p);
|
||||
continue;
|
||||
}
|
||||
(int)WGL_ARB_pixel_format.AccelerationArb,
|
||||
|
||||
// Skip formats that don't offer full hardware acceleration
|
||||
WGL_ARB_pixel_format acceleration = (WGL_ARB_pixel_format)attribs[0];
|
||||
if (acceleration != WGL_ARB_pixel_format.FullAccelerationArb)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
(int)WGL_ARB_pixel_format.RedBitsArb,
|
||||
(int)WGL_ARB_pixel_format.GreenBitsArb,
|
||||
(int)WGL_ARB_pixel_format.BlueBitsArb,
|
||||
(int)WGL_ARB_pixel_format.AlphaBitsArb,
|
||||
(int)WGL_ARB_pixel_format.ColorBitsArb,
|
||||
|
||||
(int)WGL_ARB_pixel_format.DepthBitsArb,
|
||||
(int)WGL_ARB_pixel_format.StencilBitsArb,
|
||||
|
||||
(int)WGL_ARB_multisample.SampleBuffersArb,
|
||||
(int)WGL_ARB_multisample.SamplesArb,
|
||||
|
||||
(int)WGL_ARB_pixel_format.AccumRedBitsArb,
|
||||
(int)WGL_ARB_pixel_format.AccumGreenBitsArb,
|
||||
(int)WGL_ARB_pixel_format.AccumBlueBitsArb,
|
||||
(int)WGL_ARB_pixel_format.AccumAlphaBitsArb,
|
||||
(int)WGL_ARB_pixel_format.AccumBitsArb,
|
||||
|
||||
(int)WGL_ARB_pixel_format.DoubleBufferArb,
|
||||
(int)WGL_ARB_pixel_format.StereoArb,
|
||||
0
|
||||
};
|
||||
|
||||
// Allocate storage for the results of GetPixelFormatAttrib queries
|
||||
int[] values = new int[attribs.Length];
|
||||
|
||||
// Get the format attributes for this pixel format
|
||||
if (!Wgl.Arb.GetPixelFormatAttrib(device, pixelformat, 0, attribs.Length - 1, attribs, values))
|
||||
{
|
||||
Debug.Print("[Warning] Failed to detect attributes for PixelFormat: {0}.", pixelformat);
|
||||
}
|
||||
|
||||
// Skip formats that don't offer full hardware acceleration
|
||||
WGL_ARB_pixel_format acceleration = (WGL_ARB_pixel_format)attribs[0];
|
||||
if (acceleration == WGL_ARB_pixel_format.FullAccelerationArb)
|
||||
{
|
||||
// Construct a new GraphicsMode to describe this format
|
||||
GraphicsMode mode = new GraphicsMode(new IntPtr(p),
|
||||
created_mode = new GraphicsMode(new IntPtr(pixelformat),
|
||||
new ColorFormat(values[1], values[2], values[3], values[4]),
|
||||
values[6],
|
||||
values[7],
|
||||
|
@ -235,28 +344,9 @@ namespace OpenTK.Platform.Windows
|
|||
new ColorFormat(values[10], values[11], values[12], values[13]),
|
||||
values[15] == 1 ? 2 : 1,
|
||||
values[16] == 1 ? true : false);
|
||||
|
||||
yield return mode;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ModeSelector
|
||||
|
||||
bool ModeSelector(GraphicsMode current, ColorFormat color, int depth, int stencil, int samples,
|
||||
ColorFormat accum, int buffers, bool stereo)
|
||||
{
|
||||
bool result =
|
||||
(color != ColorFormat.Empty ? current.ColorFormat >= color : true) &&
|
||||
(depth != 0 ? current.Depth >= depth : true) &&
|
||||
(stencil != 0 ? current.Stencil >= stencil : true) &&
|
||||
(samples != 0 ? current.Samples >= samples : true) &&
|
||||
(accum != ColorFormat.Empty ? current.AccumulatorFormat >= accum : true) &&
|
||||
(buffers != 0 ? current.Buffers >= buffers : true) &&
|
||||
current.Stereo == stereo;
|
||||
return result;
|
||||
return created_mode;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
|
Loading…
Reference in a new issue