Use fmtlib for formatting.
This commit is contained in:
parent
131a55c868
commit
60a7609963
|
|
@ -8,6 +8,7 @@ find_path(SCOTTT_DEBUGBREAK_INCLUDE_DIRS "debugbreak.h")
|
||||||
find_package(Vulkan REQUIRED)
|
find_package(Vulkan REQUIRED)
|
||||||
# find_package( VulkanHeaders CONFIG REQUIRED )
|
# find_package( VulkanHeaders CONFIG REQUIRED )
|
||||||
find_package(VulkanMemoryAllocator CONFIG REQUIRED)
|
find_package(VulkanMemoryAllocator CONFIG REQUIRED)
|
||||||
|
find_package(fmt CONFIG REQUIRED)
|
||||||
|
|
||||||
set(HEADER_FILES
|
set(HEADER_FILES
|
||||||
constants.h
|
constants.h
|
||||||
|
|
@ -34,6 +35,7 @@ target_link_libraries(aster_core PRIVATE glm::glm-header-only)
|
||||||
target_link_libraries(aster_core PRIVATE glfw)
|
target_link_libraries(aster_core PRIVATE glfw)
|
||||||
target_include_directories(aster_core PRIVATE ${SCOTTT_DEBUGBREAK_INCLUDE_DIRS})
|
target_include_directories(aster_core PRIVATE ${SCOTTT_DEBUGBREAK_INCLUDE_DIRS})
|
||||||
target_link_libraries(aster_core PRIVATE Vulkan::Vulkan Vulkan::Headers GPUOpen::VulkanMemoryAllocator)
|
target_link_libraries(aster_core PRIVATE Vulkan::Vulkan Vulkan::Headers GPUOpen::VulkanMemoryAllocator)
|
||||||
|
target_link_libraries(aster_core PRIVATE fmt::fmt)
|
||||||
|
|
||||||
add_executable(aster_exe "aster.cpp")
|
add_executable(aster_exe "aster.cpp")
|
||||||
target_link_libraries(aster_exe PRIVATE aster_core)
|
target_link_libraries(aster_exe PRIVATE aster_core)
|
||||||
|
|
|
||||||
|
|
@ -77,7 +77,7 @@ Device::Device(const std::string_view &_name, Context *_context, const PhysicalD
|
||||||
}
|
}
|
||||||
VERBOSE("Memory Allocator Created");
|
VERBOSE("Memory Allocator Created");
|
||||||
|
|
||||||
INFO(std::fmt("Created Device '%s' Successfully", _name.data()));
|
INFO(fmt::format("Created Device '{}' Successfully", _name));
|
||||||
|
|
||||||
// Setup queues
|
// Setup queues
|
||||||
{
|
{
|
||||||
|
|
@ -90,10 +90,10 @@ Device::Device(const std::string_view &_name, Context *_context, const PhysicalD
|
||||||
queues.present = device.getQueue(queue_families.present_idx, present_idx);
|
queues.present = device.getQueue(queue_families.present_idx, present_idx);
|
||||||
queues.transfer = device.getQueue(queue_families.transfer_idx, transfer_idx);
|
queues.transfer = device.getQueue(queue_families.transfer_idx, transfer_idx);
|
||||||
queues.compute = device.getQueue(queue_families.graphics_idx, compute_idx);
|
queues.compute = device.getQueue(queue_families.graphics_idx, compute_idx);
|
||||||
INFO(std::fmt("Graphics Queue Index: (%i, %i)", queue_families.graphics_idx, graphics_idx));
|
INFO(fmt::format("Graphics Queue Index: ({}, {})", queue_families.graphics_idx, graphics_idx));
|
||||||
INFO(std::fmt("Present Queue Index: (%i, %i)", queue_families.present_idx, present_idx));
|
INFO(fmt::format("Present Queue Index: ({}, {})", queue_families.present_idx, present_idx));
|
||||||
INFO(std::fmt("Transfer Queue Index: (%i, %i)", queue_families.transfer_idx, transfer_idx));
|
INFO(fmt::format("Transfer Queue Index: ({}, {})", queue_families.transfer_idx, transfer_idx));
|
||||||
INFO(std::fmt("Compute Queue Index: (%i, %i)", queue_families.compute_idx, compute_idx));
|
INFO(fmt::format("Compute Queue Index: ({}, {})", queue_families.compute_idx, compute_idx));
|
||||||
}
|
}
|
||||||
|
|
||||||
// transfer_cmd_pool = device.createCommandPool({
|
// transfer_cmd_pool = device.createCommandPool({
|
||||||
|
|
@ -103,7 +103,7 @@ Device::Device(const std::string_view &_name, Context *_context, const PhysicalD
|
||||||
// if (failed(result)) {
|
// if (failed(result)) {
|
||||||
// allocator.destroy();
|
// allocator.destroy();
|
||||||
// device.destroy();
|
// device.destroy();
|
||||||
// return Err::make(std::fmt("Transfer command pool creation failed with %s" CODE_LOC, to_cstr(result)), result);
|
// return Err::make(fmt::format("Transfer command pool creation failed with {}" CODE_LOC, to_cstr(result)), result);
|
||||||
// }
|
// }
|
||||||
// VERBOSE("Transfer Command Pool Created");
|
// VERBOSE("Transfer Command Pool Created");
|
||||||
//
|
//
|
||||||
|
|
@ -116,7 +116,7 @@ Device::Device(const std::string_view &_name, Context *_context, const PhysicalD
|
||||||
// device.destroyCommandPool(transfer_cmd_pool);
|
// device.destroyCommandPool(transfer_cmd_pool);
|
||||||
// allocator.destroy();
|
// allocator.destroy();
|
||||||
// device.destroy();
|
// device.destroy();
|
||||||
// return Err::make(std::fmt("Graphics command pool creation failed with %s" CODE_LOC, to_cstr(result)), result);
|
// return Err::make(fmt::format("Graphics command pool creation failed with {}" CODE_LOC, to_cstr(result)), result);
|
||||||
// }
|
// }
|
||||||
// VERBOSE("Graphics Command Pool Created");
|
// VERBOSE("Graphics Command Pool Created");
|
||||||
|
|
||||||
|
|
@ -144,16 +144,16 @@ Device::~Device() {
|
||||||
// .commandBufferCount = 1,
|
// .commandBufferCount = 1,
|
||||||
// };
|
// };
|
||||||
// if (const auto result = device.allocateCommandBuffers(&cmd_buf_alloc_info, &cmd); failed(result)) {
|
// if (const auto result = device.allocateCommandBuffers(&cmd_buf_alloc_info, &cmd); failed(result)) {
|
||||||
// return Err::make(std::fmt("Temp Command buffer allocation failed with %s" CODE_LOC, to_cstr(result)), result);
|
// return Err::make(fmt::format("Temp Command buffer allocation failed with {}" CODE_LOC, to_cstr(result)), result);
|
||||||
// }
|
// }
|
||||||
// return cmd;
|
// return cmd;
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// Res<SubmitTask<Buffer>> Device::upload_data(const Borrowed<Buffer> &_host_buffer, Buffer &&_staging_buffer) {
|
// Res<SubmitTask<Buffer>> Device::upload_data(const Borrowed<Buffer> &_host_buffer, Buffer &&_staging_buffer) {
|
||||||
// ERROR_IF(!(_host_buffer->usage & vk::BufferUsageFlagBits::eTransferDst), std::fmt("Buffer %s is not a transfer dst. Use vk::BufferUsageFlagBits::eTransferDst during creation", _host_buffer->name.data()))
|
// ERROR_IF(!(_host_buffer->usage & vk::BufferUsageFlagBits::eTransferDst), fmt::format("Buffer {} is not a transfer dst. Use vk::BufferUsageFlagBits::eTransferDst during creation", _host_buffer->name.data()))
|
||||||
// ELSE_IF_ERROR(!(_staging_buffer.usage & vk::BufferUsageFlagBits::eTransferSrc), std::fmt("Buffer %s is not a transfer src. Use vk::BufferUsageFlagBits::eTransferSrc during creation", _staging_buffer.name.data()))
|
// ELSE_IF_ERROR(!(_staging_buffer.usage & vk::BufferUsageFlagBits::eTransferSrc), fmt::format("Buffer {} is not a transfer src. Use vk::BufferUsageFlagBits::eTransferSrc during creation", _staging_buffer.name.data()))
|
||||||
// ELSE_IF_WARN(_host_buffer->memory_usage != vma::MemoryUsage::eGpuOnly, std::fmt("Memory %s is not GPU only. Upload not required", _host_buffer->name.data()))
|
// ELSE_IF_WARN(_host_buffer->memory_usage != vma::MemoryUsage::eGpuOnly, fmt::format("Memory {} is not GPU only. Upload not required", _host_buffer->name.data()))
|
||||||
// ELSE_IF_WARN(_host_buffer->memory_usage != vma::MemoryUsage::eCpuOnly, std::fmt("Memory %s is not CPU only. Staging should ideally be a CPU only buffer", _staging_buffer.name.data()));
|
// ELSE_IF_WARN(_host_buffer->memory_usage != vma::MemoryUsage::eCpuOnly, fmt::format("Memory {} is not CPU only. Staging should ideally be a CPU only buffer", _staging_buffer.name.data()));
|
||||||
//
|
//
|
||||||
// vk::CommandBuffer cmd;
|
// vk::CommandBuffer cmd;
|
||||||
// vk::CommandBufferAllocateInfo allocate_info = {
|
// vk::CommandBufferAllocateInfo allocate_info = {
|
||||||
|
|
@ -164,29 +164,29 @@ Device::~Device() {
|
||||||
//
|
//
|
||||||
// auto result = device.allocateCommandBuffers(&allocate_info, &cmd);
|
// auto result = device.allocateCommandBuffers(&allocate_info, &cmd);
|
||||||
// if (failed(result)) {
|
// if (failed(result)) {
|
||||||
// return Err::make(std::fmt("Transfer command pool allocation failed with %s" CODE_LOC, to_cstr(result)), result);
|
// return Err::make(fmt::format("Transfer command pool allocation failed with {}" CODE_LOC, to_cstr(result)), result);
|
||||||
// }
|
// }
|
||||||
// set_object_name(cmd, std::fmt("%s transfer command", _host_buffer->name.data()));
|
// set_object_name(cmd, fmt::format("{} transfer command", _host_buffer->name.data()));
|
||||||
//
|
//
|
||||||
// result = cmd.begin({
|
// result = cmd.begin({
|
||||||
// .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit,
|
// .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit,
|
||||||
// });
|
// });
|
||||||
// if (failed(result)) {
|
// if (failed(result)) {
|
||||||
// return Err::make(std::fmt("Command buffer begin failed with %s" CODE_LOC, to_cstr(result)), result);
|
// return Err::make(fmt::format("Command buffer begin failed with {}" CODE_LOC, to_cstr(result)), result);
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// cmd.copyBuffer(_staging_buffer.buffer, _host_buffer->buffer, { { .srcOffset = 0, .dstOffset = 0, .size = cast<u32>(_staging_buffer.size) } });
|
// cmd.copyBuffer(_staging_buffer.buffer, _host_buffer->buffer, { { .srcOffset = 0, .dstOffset = 0, .size = cast<u32>(_staging_buffer.size) } });
|
||||||
// result = cmd.end();
|
// result = cmd.end();
|
||||||
// if (failed(result)) {
|
// if (failed(result)) {
|
||||||
// return Err::make(std::fmt("Command buffer end failed with %s" CODE_LOC, to_cstr(result)), result);
|
// return Err::make(fmt::format("Command buffer end failed with {}" CODE_LOC, to_cstr(result)), result);
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// return SubmitTask<Buffer>::create(borrow(this), std::move(_staging_buffer), queues.transfer, transfer_cmd_pool, { cmd });
|
// return SubmitTask<Buffer>::create(borrow(this), std::move(_staging_buffer), queues.transfer, transfer_cmd_pool, { cmd });
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// Res<SubmitTask<Buffer>> Device::upload_data(const Borrowed<Buffer> &_host_buffer, const std::span<u8> &_data) {
|
// Res<SubmitTask<Buffer>> Device::upload_data(const Borrowed<Buffer> &_host_buffer, const std::span<u8> &_data) {
|
||||||
// ERROR_IF(!(_host_buffer->usage & vk::BufferUsageFlagBits::eTransferDst), std::fmt("Buffer %s is not a transfer dst. Use vk::BufferUsageFlagBits::eTransferDst during creation", _host_buffer->name.data()))
|
// ERROR_IF(!(_host_buffer->usage & vk::BufferUsageFlagBits::eTransferDst), fmt::format("Buffer {} is not a transfer dst. Use vk::BufferUsageFlagBits::eTransferDst during creation", _host_buffer->name.data()))
|
||||||
// ELSE_IF_WARN(_host_buffer->memory_usage != vma::MemoryUsage::eGpuOnly, std::fmt("Memory %s is not GPU only. Upload not required", _host_buffer->name.data()));
|
// ELSE_IF_WARN(_host_buffer->memory_usage != vma::MemoryUsage::eGpuOnly, fmt::format("Memory {} is not GPU only. Upload not required", _host_buffer->name.data()));
|
||||||
//
|
//
|
||||||
// if (auto res = Buffer::create("_stage " + _host_buffer->name, borrow(this), _data.size(), vk::BufferUsageFlagBits::eTransferSrc, vma::MemoryUsage::eCpuOnly)) {
|
// if (auto res = Buffer::create("_stage " + _host_buffer->name, borrow(this), _data.size(), vk::BufferUsageFlagBits::eTransferSrc, vma::MemoryUsage::eCpuOnly)) {
|
||||||
// return Err::make("Staging buffer creation failed", std::move(res.error()));
|
// return Err::make("Staging buffer creation failed", std::move(res.error()));
|
||||||
|
|
@ -207,7 +207,7 @@ Device::~Device() {
|
||||||
//
|
//
|
||||||
// auto [result, mapped_memory] = allocator.mapMemory(_host_buffer->allocation);
|
// auto [result, mapped_memory] = allocator.mapMemory(_host_buffer->allocation);
|
||||||
// if (failed(result)) {
|
// if (failed(result)) {
|
||||||
// return Err::make(std::fmt("Memory mapping failed with %s" CODE_LOC, to_cstr(result)), result);
|
// return Err::make(fmt::format("Memory mapping failed with {}" CODE_LOC, to_cstr(result)), result);
|
||||||
// }
|
// }
|
||||||
// memcpy(mapped_memory, _data.data(), _data.size());
|
// memcpy(mapped_memory, _data.data(), _data.size());
|
||||||
// allocator.unmapMemory(_host_buffer->allocation);
|
// allocator.unmapMemory(_host_buffer->allocation);
|
||||||
|
|
@ -216,8 +216,8 @@ Device::~Device() {
|
||||||
// }
|
// }
|
||||||
|
|
||||||
void Device::set_name(const std::string_view &_name) {
|
void Device::set_name(const std::string_view &_name) {
|
||||||
VERBOSE(std::fmt("Device %s -> %s", name.data(), _name.data()));
|
VERBOSE(fmt::format("Device {} -> {}", name.data(), _name.data()));
|
||||||
name = _name;
|
name = _name;
|
||||||
set_object_name(*physical_device.device, std::fmt("%s GPU", _name.data()));
|
set_object_name(*physical_device.device, fmt::format("{} GPU", _name.data()));
|
||||||
set_object_name(*device, std::fmt("%s Device", _name.data()));
|
set_object_name(*device, fmt::format("{} Device", _name.data()));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,60 +13,3 @@
|
||||||
|
|
||||||
// NOTE: Vulkan Dispatch Loader Storage - Should only appear once.
|
// NOTE: Vulkan Dispatch Loader Storage - Should only appear once.
|
||||||
VULKAN_HPP_DEFAULT_DISPATCH_LOADER_DYNAMIC_STORAGE
|
VULKAN_HPP_DEFAULT_DISPATCH_LOADER_DYNAMIC_STORAGE
|
||||||
|
|
||||||
//int Vsnprintf8(char* pDestination, size_t n, const char* pFormat, va_list arguments) {
|
|
||||||
// //ERROR_IF(pDestination == nullptr, "Null buffer") THEN_CRASH(1) ELSE_IF_ERROR(n == 0, "Empty buffer") THEN_CRASH(1);
|
|
||||||
//#ifdef _MSC_VER
|
|
||||||
// auto v = vsnprintf(pDestination, n, pFormat, arguments);
|
|
||||||
// ERROR_IF(v == 0, "Final requirement cannot be 0") THEN_CRASH(1);
|
|
||||||
// return v;
|
|
||||||
//#else
|
|
||||||
// return vsnprintf(pDestination, n, pFormat, arguments);
|
|
||||||
//#endif
|
|
||||||
//}
|
|
||||||
//
|
|
||||||
//int VsnprintfW(wchar_t* pDestination, size_t n, const wchar_t* pFormat, va_list arguments) {
|
|
||||||
// //ERROR_IF(pDestination == nullptr, "Null buffer") THEN_CRASH(1) ELSE_IF_ERROR(n == 0, "Empty buffer") THEN_CRASH(1);
|
|
||||||
//#ifdef _MSC_VER
|
|
||||||
// if (pDestination == nullptr && n == 0) {
|
|
||||||
// return _vscwprintf(pFormat, arguments);
|
|
||||||
// } else {
|
|
||||||
// return _vsnwprintf_s(pDestination, n, _TRUNCATE, pFormat, arguments);
|
|
||||||
// }
|
|
||||||
//#else
|
|
||||||
// char* d = new char[n + 1];
|
|
||||||
// int r = vsnprintf(d, n, convertstring<char16_t, char>(pFormat).c_str(), arguments);
|
|
||||||
// memcpy(pDestination, convertstring<char, char16_t>(d).c_str(), (n + 1) * sizeof(char16_t));
|
|
||||||
// delete[] d;
|
|
||||||
// return r;
|
|
||||||
//#endif
|
|
||||||
//}
|
|
||||||
//
|
|
||||||
//int Vsnprintf16(char16_t* pDestination, size_t n, const char16_t* pFormat, va_list arguments) {
|
|
||||||
// //ERROR_IF(pDestination == nullptr, "Null buffer") THEN_CRASH(1) ELSE_IF_ERROR(n == 0, "Empty buffer") THEN_CRASH(1);
|
|
||||||
//#ifdef _MSC_VER
|
|
||||||
// if (pDestination == nullptr && n == 0) {
|
|
||||||
// return _vscwprintf((wchar_t*)pFormat, arguments);
|
|
||||||
// } else {
|
|
||||||
// return _vsnwprintf_s((wchar_t*)pDestination, n, _TRUNCATE, (wchar_t*)pFormat, arguments);
|
|
||||||
// }
|
|
||||||
//#else
|
|
||||||
// char* d = new char[n + 1];
|
|
||||||
// int r = vsnprintf(d, n, convertstring<char16_t, char>(pFormat).c_str(), arguments);
|
|
||||||
// memcpy(pDestination, convertstring<char, char16_t>(d).c_str(), (n + 1) * sizeof(char16_t));
|
|
||||||
// delete[] d;
|
|
||||||
// return r;
|
|
||||||
//#endif
|
|
||||||
//}
|
|
||||||
|
|
||||||
std::string std::impl::format(const char *_fmt, ...) {
|
|
||||||
va_list args;
|
|
||||||
va_start(args, _fmt);
|
|
||||||
|
|
||||||
const auto req = vsnprintf(nullptr, 0, _fmt, args) + 1;
|
|
||||||
string buf(req, '\0');
|
|
||||||
vsnprintf(buf.data(), buf.size(), _fmt, args);
|
|
||||||
|
|
||||||
va_end(args);
|
|
||||||
return buf;
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@
|
||||||
#include <glm/glm.hpp>
|
#include <glm/glm.hpp>
|
||||||
#include <glm/gtc/matrix_transform.hpp>
|
#include <glm/gtc/matrix_transform.hpp>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <fmt/core.h>
|
||||||
|
|
||||||
#define VULKAN_HPP_ASSERT(expr) DEBUG_IF(!(expr), "Vulkan assert failed")
|
#define VULKAN_HPP_ASSERT(expr) DEBUG_IF(!(expr), "Vulkan assert failed")
|
||||||
#include <vk_mem_alloc.h>
|
#include <vk_mem_alloc.h>
|
||||||
|
|
@ -23,17 +24,6 @@
|
||||||
return _result != vk::Result::eSuccess;
|
return _result != vk::Result::eSuccess;
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace std {
|
|
||||||
namespace impl {
|
|
||||||
string format(const char *_fmt, ...);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename... Ts>
|
|
||||||
[[nodiscard]] string fmt(const char *_fmt, Ts &&..._args) {
|
|
||||||
return impl::format(_fmt, forward<Ts>(_args)...);
|
|
||||||
}
|
|
||||||
} // namespace std
|
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
concept IsVkEnum = requires(T _t) {
|
concept IsVkEnum = requires(T _t) {
|
||||||
{ std::is_enum_v<T> };
|
{ std::is_enum_v<T> };
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ QueueFamilyIndices PhysicalDevice::get_queue_families(const Window *_window, con
|
||||||
u32 family_index = 0;
|
u32 family_index = 0;
|
||||||
for (const auto &queue_family : queue_families_) {
|
for (const auto &queue_family : queue_families_) {
|
||||||
u32 this_family_count = 0;
|
u32 this_family_count = 0;
|
||||||
VERBOSE(std::fmt("Queue(%i): %s", family_index, to_string(queue_family.queueFlags).data()));
|
VERBOSE(fmt::format("Queue({}): {}", family_index, to_string(queue_family.queueFlags).data()));
|
||||||
|
|
||||||
if (!indices.has_graphics() && (queue_family.queueFlags & vk::QueueFlagBits::eGraphics)) {
|
if (!indices.has_graphics() && (queue_family.queueFlags & vk::QueueFlagBits::eGraphics)) {
|
||||||
if (queue_family.queueCount > this_family_count) {
|
if (queue_family.queueCount > this_family_count) {
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ Window::Window(const std::string_view &_title, Context *_context, const vk::Exte
|
||||||
|
|
||||||
window = glfwCreateWindow(cast<i32>(extent.width), cast<i32>(extent.height), name.c_str(), (_full_screen ? monitor : nullptr), nullptr);
|
window = glfwCreateWindow(cast<i32>(extent.width), cast<i32>(extent.height), name.c_str(), (_full_screen ? monitor : nullptr), nullptr);
|
||||||
ERROR_IF(window == nullptr, "Window creation failed")
|
ERROR_IF(window == nullptr, "Window creation failed")
|
||||||
ELSE_INFO(std::fmt("Window '%s' created with resolution '%dx%d'", name.data(), extent.width, extent.height));
|
ELSE_INFO(fmt::format("Window '{}' created with resolution '{}x{}'", name.data(), extent.width, extent.height));
|
||||||
if (window == nullptr) {
|
if (window == nullptr) {
|
||||||
auto code = GlfwContext::post_error();
|
auto code = GlfwContext::post_error();
|
||||||
glfwTerminate();
|
glfwTerminate();
|
||||||
|
|
@ -71,5 +71,5 @@ Window::~Window() {
|
||||||
}
|
}
|
||||||
monitor = nullptr;
|
monitor = nullptr;
|
||||||
|
|
||||||
INFO("Window '" + name + "' Destroyed");
|
INFO(fmt::format("Window '{}' Destroyed", name));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
"glfw3",
|
"glfw3",
|
||||||
"glm",
|
"glm",
|
||||||
"scottt-debugbreak",
|
"scottt-debugbreak",
|
||||||
"vulkan-memory-allocator"
|
"vulkan-memory-allocator",
|
||||||
|
"fmt"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue