4d2251a06b
git-svn-id: https://svn.code.sf.net/p/fteqw/code/trunk@6141 fc73d0e0-1445-4013-8a0c-d673dee63da5
5498 lines
193 KiB
C
5498 lines
193 KiB
C
#include "quakedef.h"
|
||
#ifdef VKQUAKE
|
||
#include "vkrenderer.h"
|
||
#include "gl_draw.h"
|
||
#include "shader.h"
|
||
#include "renderque.h" //is anything still using this?
|
||
|
||
#include "vr.h"
|
||
|
||
#define VK_API_MAX_VERSION VK_API_VERSION_1_0
|
||
|
||
extern qboolean vid_isfullscreen;
|
||
|
||
cvar_t vk_stagingbuffers = CVARFD ("vk_stagingbuffers", "", CVAR_RENDERERLATCH, "Configures which dynamic buffers are copied into gpu memory for rendering, instead of reading from shared memory. Empty for default settings.\nAccepted chars are u(niform), e(lements), v(ertex), 0(none).");
|
||
static cvar_t vk_submissionthread = CVARD ("vk_submissionthread", "", "Execute submits+presents on a thread dedicated to executing them. This may be a significant speedup on certain drivers.");
|
||
static cvar_t vk_debug = CVARFD("vk_debug", "0", CVAR_VIDEOLATCH, "Register a debug handler to display driver/layer messages. 2 enables the standard validation layers.");
|
||
static cvar_t vk_dualqueue = CVARFD("vk_dualqueue", "", CVAR_VIDEOLATCH, "Attempt to use a separate queue for presentation. Blank for default.");
|
||
static cvar_t vk_busywait = CVARD ("vk_busywait", "", "Force busy waiting until the GPU finishes doing its thing.");
|
||
static cvar_t vk_waitfence = CVARD ("vk_waitfence", "", "Waits on fences, instead of semaphores. This is more likely to result in gpu stalls while the cpu waits.");
|
||
static cvar_t vk_usememorypools = CVARFD("vk_usememorypools", "", CVAR_VIDEOLATCH, "Allocates memory pools for sub allocations. Vulkan has a limit to the number of memory allocations allowed so this should always be enabled, however at this time FTE is unable to reclaim pool memory, and would require periodic vid_restarts to flush them.");
|
||
static cvar_t vk_khr_get_memory_requirements2 = CVARFD("vk_khr_get_memory_requirements2", "", CVAR_VIDEOLATCH, "Enable extended memory info querires");
|
||
static cvar_t vk_khr_dedicated_allocation = CVARFD("vk_khr_dedicated_allocation", "", CVAR_VIDEOLATCH, "Flag vulkan memory allocations as dedicated, where applicable.");
|
||
static cvar_t vk_khr_push_descriptor = CVARFD("vk_khr_push_descriptor", "", CVAR_VIDEOLATCH, "Enables better descriptor streaming.");
|
||
static cvar_t vk_amd_rasterization_order = CVARFD("vk_amd_rasterization_order", "", CVAR_VIDEOLATCH, "Enables the use of relaxed rasterization ordering, for a small speedup at the minor risk of a little zfighting.");
|
||
#ifdef VK_EXT_astc_decode_mode
|
||
static cvar_t vk_ext_astc_decode_mode = CVARFD("vk_ext_astc_decode_mode", "", CVAR_VIDEOLATCH, "Enables reducing texture cache sizes for LDR ASTC-compressed textures.");
|
||
#endif
|
||
extern cvar_t vid_srgb, vid_vsync, vid_triplebuffer, r_stereo_method, vid_multisample, vid_bpp;
|
||
|
||
texid_t r_blackcubeimage, r_whitecubeimage;
|
||
|
||
|
||
void VK_RegisterVulkanCvars(void)
|
||
{
|
||
#define VKRENDEREROPTIONS "Vulkan-Specific Renderer Options"
|
||
Cvar_Register (&vk_stagingbuffers, VKRENDEREROPTIONS);
|
||
Cvar_Register (&vk_submissionthread, VKRENDEREROPTIONS);
|
||
Cvar_Register (&vk_debug, VKRENDEREROPTIONS);
|
||
Cvar_Register (&vk_dualqueue, VKRENDEREROPTIONS);
|
||
Cvar_Register (&vk_busywait, VKRENDEREROPTIONS);
|
||
Cvar_Register (&vk_waitfence, VKRENDEREROPTIONS);
|
||
Cvar_Register (&vk_usememorypools, VKRENDEREROPTIONS);
|
||
|
||
Cvar_Register (&vk_khr_get_memory_requirements2,VKRENDEREROPTIONS);
|
||
Cvar_Register (&vk_khr_dedicated_allocation,VKRENDEREROPTIONS);
|
||
Cvar_Register (&vk_khr_push_descriptor, VKRENDEREROPTIONS);
|
||
Cvar_Register (&vk_amd_rasterization_order, VKRENDEREROPTIONS);
|
||
#ifdef VK_EXT_astc_decode_mode
|
||
Cvar_Register (&vk_ext_astc_decode_mode, VKRENDEREROPTIONS);
|
||
#endif
|
||
}
|
||
void R2D_Console_Resize(void);
|
||
static void VK_DestroySampler(VkSampler s);
|
||
|
||
extern qboolean scr_con_forcedraw;
|
||
|
||
#ifndef MULTITHREAD
|
||
#define Sys_LockConditional(c)
|
||
#define Sys_UnlockConditional(c)
|
||
#endif
|
||
|
||
const char *vklayerlist[] =
|
||
{
|
||
#if 1
|
||
"VK_LAYER_KHRONOS_validation"
|
||
#elif 1
|
||
"VK_LAYER_LUNARG_standard_validation"
|
||
#else
|
||
//older versions of the sdk were crashing out on me,
|
||
// "VK_LAYER_LUNARG_api_dump",
|
||
"VK_LAYER_LUNARG_device_limits",
|
||
//"VK_LAYER_LUNARG_draw_state",
|
||
"VK_LAYER_LUNARG_image",
|
||
//"VK_LAYER_LUNARG_mem_tracker",
|
||
"VK_LAYER_LUNARG_object_tracker",
|
||
"VK_LAYER_LUNARG_param_checker",
|
||
"VK_LAYER_LUNARG_screenshot",
|
||
"VK_LAYER_LUNARG_swapchain",
|
||
"VK_LAYER_GOOGLE_threading",
|
||
"VK_LAYER_GOOGLE_unique_objects",
|
||
//"VK_LAYER_LUNARG_vktrace",
|
||
#endif
|
||
};
|
||
#define vklayercount (vk_debug.ival>1?countof(vklayerlist):0)
|
||
|
||
|
||
//code to initialise+destroy vulkan contexts.
|
||
//this entire file is meant to be platform-agnostic.
|
||
//the vid code still needs to set up vkGetInstanceProcAddr, and do all the window+input stuff.
|
||
|
||
#ifdef VK_NO_PROTOTYPES
|
||
#define VKFunc(n) PFN_vk##n vk##n;
|
||
#ifdef VK_EXT_debug_utils
|
||
VKFunc(CreateDebugUtilsMessengerEXT)
|
||
VKFunc(DestroyDebugUtilsMessengerEXT)
|
||
#endif
|
||
#ifdef VK_EXT_debug_report
|
||
VKFunc(CreateDebugReportCallbackEXT)
|
||
VKFunc(DestroyDebugReportCallbackEXT)
|
||
#endif
|
||
VKFuncs
|
||
#undef VKFunc
|
||
#endif
|
||
|
||
void VK_Submit_Work(VkCommandBuffer cmdbuf, VkSemaphore semwait, VkPipelineStageFlags semwaitstagemask, VkSemaphore semsignal, VkFence fencesignal, struct vkframe *presentframe, struct vk_fencework *fencedwork);
|
||
#ifdef MULTITHREAD
|
||
static int VK_Submit_Thread(void *arg);
|
||
#endif
|
||
static void VK_Submit_DoWork(void);
|
||
|
||
static void VK_DestroyRenderPasses(void);
|
||
VkRenderPass VK_GetRenderPass(int pass);
|
||
static void VK_Shutdown_PostProc(void);
|
||
|
||
struct vulkaninfo_s vk;
|
||
static struct vk_rendertarg postproc[4];
|
||
static unsigned int postproc_buf;
|
||
static struct vk_rendertarg_cube vk_rt_cubemap;
|
||
|
||
qboolean VK_SCR_GrabBackBuffer(void);
|
||
|
||
#if defined(__linux__) && defined(__GLIBC__)
|
||
#include <execinfo.h>
|
||
#define DOBACKTRACE() \
|
||
do { \
|
||
void *bt[16]; \
|
||
int i, fr = backtrace(bt, countof(bt)); \
|
||
char **strings = backtrace_symbols(bt, fr); \
|
||
for (i = 0; i < fr; i++) \
|
||
if (strings) \
|
||
Con_Printf("\t%s\n", strings[i]); \
|
||
else \
|
||
Con_Printf("\t%p\n", bt[i]); \
|
||
free(strings); \
|
||
} while(0)
|
||
#else
|
||
#define DOBACKTRACE()
|
||
#endif
|
||
|
||
char *VK_VKErrorToString(VkResult err)
|
||
{
|
||
switch(err)
|
||
{
|
||
//positive codes
|
||
case VK_SUCCESS: return "VK_SUCCESS";
|
||
case VK_NOT_READY: return "VK_NOT_READY";
|
||
case VK_TIMEOUT: return "VK_TIMEOUT";
|
||
case VK_EVENT_SET: return "VK_EVENT_SET";
|
||
case VK_EVENT_RESET: return "VK_EVENT_RESET";
|
||
case VK_INCOMPLETE: return "VK_INCOMPLETE";
|
||
|
||
//core errors
|
||
case VK_ERROR_OUT_OF_HOST_MEMORY: return "VK_ERROR_OUT_OF_HOST_MEMORY";
|
||
case VK_ERROR_OUT_OF_DEVICE_MEMORY: return "VK_ERROR_OUT_OF_DEVICE_MEMORY";
|
||
case VK_ERROR_INITIALIZATION_FAILED: return "VK_ERROR_INITIALIZATION_FAILED";
|
||
case VK_ERROR_DEVICE_LOST: return "VK_ERROR_DEVICE_LOST"; //by far the most common.
|
||
case VK_ERROR_MEMORY_MAP_FAILED: return "VK_ERROR_MEMORY_MAP_FAILED";
|
||
case VK_ERROR_LAYER_NOT_PRESENT: return "VK_ERROR_LAYER_NOT_PRESENT";
|
||
case VK_ERROR_EXTENSION_NOT_PRESENT: return "VK_ERROR_EXTENSION_NOT_PRESENT";
|
||
case VK_ERROR_FEATURE_NOT_PRESENT: return "VK_ERROR_FEATURE_NOT_PRESENT";
|
||
case VK_ERROR_INCOMPATIBLE_DRIVER: return "VK_ERROR_INCOMPATIBLE_DRIVER";
|
||
case VK_ERROR_TOO_MANY_OBJECTS: return "VK_ERROR_TOO_MANY_OBJECTS";
|
||
case VK_ERROR_FORMAT_NOT_SUPPORTED: return "VK_ERROR_FORMAT_NOT_SUPPORTED";
|
||
case VK_ERROR_FRAGMENTED_POOL: return "VK_ERROR_FRAGMENTED_POOL";
|
||
|
||
case VK_ERROR_SURFACE_LOST_KHR: return "VK_ERROR_SURFACE_LOST_KHR";
|
||
case VK_ERROR_NATIVE_WINDOW_IN_USE_KHR: return "VK_ERROR_NATIVE_WINDOW_IN_USE_KHR";
|
||
case VK_SUBOPTIMAL_KHR: return "VK_SUBOPTIMAL_KHR";
|
||
case VK_ERROR_OUT_OF_DATE_KHR: return "VK_ERROR_OUT_OF_DATE_KHR";
|
||
case VK_ERROR_INCOMPATIBLE_DISPLAY_KHR: return "VK_ERROR_INCOMPATIBLE_DISPLAY_KHR";
|
||
|
||
case VK_ERROR_VALIDATION_FAILED_EXT: return "VK_ERROR_VALIDATION_FAILED_EXT";
|
||
case VK_ERROR_INVALID_SHADER_NV: return "VK_ERROR_INVALID_SHADER_NV";
|
||
case VK_ERROR_OUT_OF_POOL_MEMORY_KHR: return "VK_ERROR_OUT_OF_POOL_MEMORY_KHR";
|
||
case VK_ERROR_INVALID_EXTERNAL_HANDLE_KHR: return "VK_ERROR_INVALID_EXTERNAL_HANDLE_KHR";
|
||
|
||
#ifdef VK_EXT_image_drm_format_modifier
|
||
case VK_ERROR_INVALID_DRM_FORMAT_MODIFIER_PLANE_LAYOUT_EXT: return "VK_ERROR_OUT_OF_POOL_MEMORY_KHR";
|
||
#endif
|
||
#ifdef VK_EXT_descriptor_indexing
|
||
case VK_ERROR_FRAGMENTATION_EXT: return "VK_ERROR_OUT_OF_POOL_MEMORY_KHR";
|
||
#endif
|
||
#ifdef VK_EXT_global_priority
|
||
case VK_ERROR_NOT_PERMITTED_EXT: return "VK_ERROR_OUT_OF_POOL_MEMORY_KHR";
|
||
#endif
|
||
#ifdef VK_EXT_buffer_device_address
|
||
case VK_ERROR_INVALID_DEVICE_ADDRESS_EXT: return "VK_ERROR_OUT_OF_POOL_MEMORY_KHR";
|
||
#endif
|
||
|
||
//irrelevant parts of the enum
|
||
// case VK_RESULT_RANGE_SIZE:
|
||
case VK_RESULT_MAX_ENUM:
|
||
default:
|
||
break;
|
||
}
|
||
return va("%d", (int)err);
|
||
}
|
||
#ifdef VK_EXT_debug_utils
|
||
static void DebugSetName(VkObjectType objtype, uint64_t handle, const char *name)
|
||
{
|
||
if (vkSetDebugUtilsObjectNameEXT)
|
||
{
|
||
VkDebugUtilsObjectNameInfoEXT info =
|
||
{
|
||
VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT,
|
||
NULL,
|
||
objtype,
|
||
handle,
|
||
name
|
||
};
|
||
vkSetDebugUtilsObjectNameEXT(vk.device, &info);
|
||
}
|
||
}
|
||
static VkDebugUtilsMessengerEXT vk_debugucallback;
|
||
char *DebugAnnotObjectToString(VkObjectType t)
|
||
{
|
||
switch(t)
|
||
{
|
||
case VK_OBJECT_TYPE_UNKNOWN: return "VK_OBJECT_TYPE_UNKNOWN";
|
||
case VK_OBJECT_TYPE_INSTANCE: return "VK_OBJECT_TYPE_INSTANCE";
|
||
case VK_OBJECT_TYPE_PHYSICAL_DEVICE: return "VK_OBJECT_TYPE_PHYSICAL_DEVICE";
|
||
case VK_OBJECT_TYPE_DEVICE: return "VK_OBJECT_TYPE_DEVICE";
|
||
case VK_OBJECT_TYPE_QUEUE: return "VK_OBJECT_TYPE_QUEUE";
|
||
case VK_OBJECT_TYPE_SEMAPHORE: return "VK_OBJECT_TYPE_SEMAPHORE";
|
||
case VK_OBJECT_TYPE_COMMAND_BUFFER: return "VK_OBJECT_TYPE_COMMAND_BUFFER";
|
||
case VK_OBJECT_TYPE_FENCE: return "VK_OBJECT_TYPE_FENCE";
|
||
case VK_OBJECT_TYPE_DEVICE_MEMORY: return "VK_OBJECT_TYPE_DEVICE_MEMORY";
|
||
case VK_OBJECT_TYPE_BUFFER: return "VK_OBJECT_TYPE_BUFFER";
|
||
case VK_OBJECT_TYPE_IMAGE: return "VK_OBJECT_TYPE_IMAGE";
|
||
case VK_OBJECT_TYPE_EVENT: return "VK_OBJECT_TYPE_EVENT";
|
||
case VK_OBJECT_TYPE_QUERY_POOL: return "VK_OBJECT_TYPE_QUERY_POOL";
|
||
case VK_OBJECT_TYPE_BUFFER_VIEW: return "VK_OBJECT_TYPE_BUFFER_VIEW";
|
||
case VK_OBJECT_TYPE_IMAGE_VIEW: return "VK_OBJECT_TYPE_IMAGE_VIEW";
|
||
case VK_OBJECT_TYPE_SHADER_MODULE: return "VK_OBJECT_TYPE_SHADER_MODULE";
|
||
case VK_OBJECT_TYPE_PIPELINE_CACHE: return "VK_OBJECT_TYPE_PIPELINE_CACHE";
|
||
case VK_OBJECT_TYPE_PIPELINE_LAYOUT: return "VK_OBJECT_TYPE_PIPELINE_LAYOUT";
|
||
case VK_OBJECT_TYPE_RENDER_PASS: return "VK_OBJECT_TYPE_RENDER_PASS";
|
||
case VK_OBJECT_TYPE_PIPELINE: return "VK_OBJECT_TYPE_PIPELINE";
|
||
case VK_OBJECT_TYPE_DESCRIPTOR_SET_LAYOUT: return "VK_OBJECT_TYPE_DESCRIPTOR_SET_LAYOUT";
|
||
case VK_OBJECT_TYPE_SAMPLER: return "VK_OBJECT_TYPE_SAMPLER";
|
||
case VK_OBJECT_TYPE_DESCRIPTOR_POOL: return "VK_OBJECT_TYPE_DESCRIPTOR_POOL";
|
||
case VK_OBJECT_TYPE_DESCRIPTOR_SET: return "VK_OBJECT_TYPE_DESCRIPTOR_SET";
|
||
case VK_OBJECT_TYPE_FRAMEBUFFER: return "VK_OBJECT_TYPE_FRAMEBUFFER";
|
||
case VK_OBJECT_TYPE_COMMAND_POOL: return "VK_OBJECT_TYPE_COMMAND_POOL";
|
||
case VK_OBJECT_TYPE_SAMPLER_YCBCR_CONVERSION: return "VK_OBJECT_TYPE_SAMPLER_YCBCR_CONVERSION";
|
||
case VK_OBJECT_TYPE_DESCRIPTOR_UPDATE_TEMPLATE: return "VK_OBJECT_TYPE_DESCRIPTOR_UPDATE_TEMPLATE";
|
||
case VK_OBJECT_TYPE_SURFACE_KHR: return "VK_OBJECT_TYPE_SURFACE_KHR";
|
||
case VK_OBJECT_TYPE_SWAPCHAIN_KHR: return "VK_OBJECT_TYPE_SWAPCHAIN_KHR";
|
||
case VK_OBJECT_TYPE_DISPLAY_KHR: return "VK_OBJECT_TYPE_DISPLAY_KHR";
|
||
case VK_OBJECT_TYPE_DISPLAY_MODE_KHR: return "VK_OBJECT_TYPE_DISPLAY_MODE_KHR";
|
||
case VK_OBJECT_TYPE_DEBUG_REPORT_CALLBACK_EXT: return "VK_OBJECT_TYPE_DEBUG_REPORT_CALLBACK_EXT";
|
||
#ifdef VK_NVX_device_generated_commands
|
||
case VK_OBJECT_TYPE_OBJECT_TABLE_NVX: return "VK_OBJECT_TYPE_OBJECT_TABLE_NVX";
|
||
case VK_OBJECT_TYPE_INDIRECT_COMMANDS_LAYOUT_NVX: return "VK_OBJECT_TYPE_INDIRECT_COMMANDS_LAYOUT_NVX";
|
||
#endif
|
||
case VK_OBJECT_TYPE_DEBUG_UTILS_MESSENGER_EXT: return "VK_OBJECT_TYPE_DEBUG_UTILS_MESSENGER_EXT";
|
||
case VK_OBJECT_TYPE_VALIDATION_CACHE_EXT: return "VK_OBJECT_TYPE_VALIDATION_CACHE_EXT";
|
||
#ifdef VK_NV_ray_tracing
|
||
case VK_OBJECT_TYPE_ACCELERATION_STRUCTURE_NV: return "VK_OBJECT_TYPE_ACCELERATION_STRUCTURE_NV";
|
||
#endif
|
||
// case VK_OBJECT_TYPE_RANGE_SIZE:
|
||
case VK_OBJECT_TYPE_MAX_ENUM:
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
return "UNKNOWNTYPE";
|
||
}
|
||
static VKAPI_ATTR VkBool32 VKAPI_CALL mydebugutilsmessagecallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageType, const VkDebugUtilsMessengerCallbackDataEXT*pCallbackData, void* pUserData)
|
||
{
|
||
char prefix[64];
|
||
int l = 0; //developer level
|
||
if (messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT)
|
||
{ //spam?
|
||
strcpy(prefix, "VERBOSE:");
|
||
l = 2;
|
||
}
|
||
else if (messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT)
|
||
{ //generally stuff like 'object created'
|
||
strcpy(prefix, "INFO:");
|
||
l = 1;
|
||
}
|
||
else if (messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT)
|
||
strcpy(prefix, CON_WARNING"WARNING:");
|
||
else if (messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT)
|
||
strcpy(prefix, CON_ERROR "ERROR:");
|
||
|
||
if (messageType & VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT)
|
||
strcat(prefix, "GENERAL");
|
||
else
|
||
{
|
||
if (messageType & VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT)
|
||
strcat(prefix, "SPEC");
|
||
if (messageType & VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT)
|
||
{
|
||
if (messageType & VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT)
|
||
{
|
||
strcat(prefix, "|");
|
||
}
|
||
strcat(prefix,"PERF");
|
||
}
|
||
}
|
||
Con_DLPrintf(l, "%s[%d] %s - %s\n", prefix, pCallbackData->messageIdNumber, pCallbackData->pMessageIdName?pCallbackData->pMessageIdName:"", pCallbackData->pMessage);
|
||
|
||
if (pCallbackData->objectCount > 0)
|
||
{
|
||
uint32_t object;
|
||
for(object = 0; object < pCallbackData->objectCount; ++object)
|
||
Con_DLPrintf(l, " Object[%d] - Type %s, Value %"PRIx64", Name \"%s\"\n", object,
|
||
DebugAnnotObjectToString(pCallbackData->pObjects[object].objectType),
|
||
pCallbackData->pObjects[object].objectHandle,
|
||
pCallbackData->pObjects[object].pObjectName);
|
||
}
|
||
|
||
if (pCallbackData->cmdBufLabelCount > 0)
|
||
{
|
||
uint32_t label;
|
||
for (label = 0; label < pCallbackData->cmdBufLabelCount; ++label)
|
||
Con_DLPrintf(l, " Label[%d] - %s { %f, %f, %f, %f}\n", label,
|
||
pCallbackData->pCmdBufLabels[label].pLabelName,
|
||
pCallbackData->pCmdBufLabels[label].color[0],
|
||
pCallbackData->pCmdBufLabels[label].color[1],
|
||
pCallbackData->pCmdBufLabels[label].color[2],
|
||
pCallbackData->pCmdBufLabels[label].color[3]);
|
||
}
|
||
return false;
|
||
}
|
||
#else
|
||
#define DebugSetName(objtype,handle,name)
|
||
#endif
|
||
#ifdef VK_EXT_debug_report
|
||
static VkDebugReportCallbackEXT vk_debugcallback;
|
||
static VkBool32 VKAPI_PTR mydebugreportcallback(
|
||
VkDebugReportFlagsEXT flags,
|
||
VkDebugReportObjectTypeEXT objectType,
|
||
uint64_t object,
|
||
size_t location,
|
||
int32_t messageCode,
|
||
const char* pLayerPrefix,
|
||
const char* pMessage,
|
||
void* pUserData)
|
||
{
|
||
if (flags & VK_DEBUG_REPORT_ERROR_BIT_EXT)
|
||
{
|
||
Con_Printf("ERR: %s: %s\n", pLayerPrefix, pMessage);
|
||
// DOBACKTRACE();
|
||
}
|
||
else if (flags & VK_DEBUG_REPORT_WARNING_BIT_EXT)
|
||
{
|
||
if (!strncmp(pMessage, "Additional bits in Source accessMask", 36) && strstr(pMessage, "VK_IMAGE_LAYOUT_UNDEFINED"))
|
||
return false; //I don't give a fuck. undefined can be used to change layouts on a texture that already exists too.
|
||
Con_Printf("WARN: %s: %s\n", pLayerPrefix, pMessage);
|
||
DOBACKTRACE();
|
||
}
|
||
else if (flags & VK_DEBUG_REPORT_DEBUG_BIT_EXT)
|
||
{
|
||
Con_DPrintf("DBG: %s: %s\n", pLayerPrefix, pMessage);
|
||
// DOBACKTRACE();
|
||
}
|
||
else if (flags & VK_DEBUG_REPORT_INFORMATION_BIT_EXT)
|
||
{
|
||
#ifdef _WIN32
|
||
// OutputDebugString(va("INF: %s\n", pMessage));
|
||
#else
|
||
Con_Printf("INF: %s: %s\n", pLayerPrefix, pMessage);
|
||
// DOBACKTRACE();
|
||
#endif
|
||
}
|
||
else if (flags & VK_DEBUG_REPORT_PERFORMANCE_WARNING_BIT_EXT)
|
||
{
|
||
Con_Printf("PERF: %s: %s\n", pLayerPrefix, pMessage);
|
||
DOBACKTRACE();
|
||
}
|
||
else
|
||
{
|
||
Con_Printf("OTHER: %s: %s\n", pLayerPrefix, pMessage);
|
||
DOBACKTRACE();
|
||
}
|
||
return false;
|
||
}
|
||
#endif
|
||
|
||
//typeBits is some vulkan requirement thing (like textures must be device-local).
|
||
//requirements_mask are things that the engine may require (like host-visible).
|
||
//note that there is absolutely no guarentee that hardware requirements will match what the host needs.
|
||
//thus you may need to use staging.
|
||
uint32_t vk_find_memory_try(uint32_t typeBits, VkFlags requirements_mask)
|
||
{
|
||
uint32_t i;
|
||
for (i = 0; i < 32; i++)
|
||
{
|
||
if ((typeBits & 1) == 1)
|
||
{
|
||
if ((vk.memory_properties.memoryTypes[i].propertyFlags & requirements_mask) == requirements_mask)
|
||
return i;
|
||
}
|
||
typeBits >>= 1;
|
||
}
|
||
return ~0u;
|
||
}
|
||
uint32_t vk_find_memory_require(uint32_t typeBits, VkFlags requirements_mask)
|
||
{
|
||
uint32_t ret = vk_find_memory_try(typeBits, requirements_mask);
|
||
if (ret == ~0)
|
||
Sys_Error("Unable to find suitable vulkan memory pool\n");
|
||
return ret;
|
||
}
|
||
|
||
void VK_DestroyVkTexture(vk_image_t *img)
|
||
{
|
||
if (!img)
|
||
return;
|
||
if (img->sampler)
|
||
VK_DestroySampler(img->sampler);
|
||
if (img->view)
|
||
vkDestroyImageView(vk.device, img->view, vkallocationcb);
|
||
if (img->image)
|
||
vkDestroyImage(vk.device, img->image, vkallocationcb);
|
||
VK_ReleasePoolMemory(&img->mem);
|
||
}
|
||
static void VK_DestroyVkTexture_Delayed(void *w)
|
||
{
|
||
VK_DestroyVkTexture(w);
|
||
}
|
||
|
||
static void VK_DestroySwapChain(void)
|
||
{
|
||
uint32_t i;
|
||
|
||
#ifdef MULTITHREAD
|
||
if (vk.submitcondition)
|
||
{
|
||
Sys_LockConditional(vk.submitcondition);
|
||
vk.neednewswapchain = true;
|
||
Sys_ConditionSignal(vk.submitcondition);
|
||
Sys_UnlockConditional(vk.submitcondition);
|
||
}
|
||
if (vk.submitthread)
|
||
{
|
||
Sys_WaitOnThread(vk.submitthread);
|
||
vk.submitthread = NULL;
|
||
}
|
||
#endif
|
||
while (vk.work)
|
||
{
|
||
Sys_LockConditional(vk.submitcondition);
|
||
VK_Submit_DoWork();
|
||
Sys_UnlockConditional(vk.submitcondition);
|
||
}
|
||
if (vk.dopresent)
|
||
vk.dopresent(NULL);
|
||
if (vk.device)
|
||
vkDeviceWaitIdle(vk.device);
|
||
/*while (vk.aquirenext < vk.aquirelast)
|
||
{
|
||
VkWarnAssert(vkWaitForFences(vk.device, 1, &vk.acquirefences[vk.aquirenext%ACQUIRELIMIT], VK_FALSE, UINT64_MAX));
|
||
vk.aquirenext++;
|
||
}*/
|
||
VK_FencedCheck();
|
||
while(vk.frameendjobs)
|
||
{ //we've fully synced the gpu now, we can clean up any resources that were pending but not assigned yet.
|
||
struct vk_frameend *job = vk.frameendjobs;
|
||
vk.frameendjobs = job->next;
|
||
job->FrameEnded(job+1);
|
||
Z_Free(job);
|
||
}
|
||
|
||
if (vk.frame)
|
||
{
|
||
vk.frame->next = vk.unusedframes;
|
||
vk.unusedframes = vk.frame;
|
||
vk.frame = NULL;
|
||
}
|
||
|
||
if (vk.dopresent)
|
||
vk.dopresent(NULL);
|
||
|
||
//wait for it to all finish first...
|
||
if (vk.device)
|
||
vkDeviceWaitIdle(vk.device);
|
||
#if 0 //don't bother waiting as they're going to be destroyed anyway, and we're having a lot of fun with drivers that don't bother signalling them on teardown
|
||
vk.acquirenext = vk.acquirelast;
|
||
#else
|
||
//clean up our acquires so we know the driver isn't going to update anything.
|
||
while (vk.acquirenext < vk.acquirelast)
|
||
{
|
||
if (vk.acquirefences[vk.acquirenext%ACQUIRELIMIT])
|
||
VkWarnAssert(vkWaitForFences(vk.device, 1, &vk.acquirefences[vk.acquirenext%ACQUIRELIMIT], VK_FALSE, 1000000000u)); //drivers suck, especially in times of error, and especially if its nvidia's vulkan driver.
|
||
vk.acquirenext++;
|
||
}
|
||
#endif
|
||
for (i = 0; i < ACQUIRELIMIT; i++)
|
||
{
|
||
if (vk.acquirefences[i])
|
||
vkDestroyFence(vk.device, vk.acquirefences[i], vkallocationcb);
|
||
vk.acquirefences[i] = VK_NULL_HANDLE;
|
||
}
|
||
|
||
for (i = 0; i < vk.backbuf_count; i++)
|
||
{
|
||
//swapchain stuff
|
||
if (vk.backbufs[i].framebuffer)
|
||
vkDestroyFramebuffer(vk.device, vk.backbufs[i].framebuffer, vkallocationcb);
|
||
vk.backbufs[i].framebuffer = VK_NULL_HANDLE;
|
||
if (vk.backbufs[i].colour.view)
|
||
vkDestroyImageView(vk.device, vk.backbufs[i].colour.view, vkallocationcb);
|
||
vk.backbufs[i].colour.view = VK_NULL_HANDLE;
|
||
VK_DestroyVkTexture(&vk.backbufs[i].depth);
|
||
VK_DestroyVkTexture(&vk.backbufs[i].mscolour);
|
||
vkDestroySemaphore(vk.device, vk.backbufs[i].presentsemaphore, vkallocationcb);
|
||
}
|
||
|
||
while(vk.unusedframes)
|
||
{
|
||
struct vkframe *frame = vk.unusedframes;
|
||
vk.unusedframes = frame->next;
|
||
|
||
VKBE_ShutdownFramePools(frame);
|
||
|
||
vkFreeCommandBuffers(vk.device, vk.cmdpool, frame->maxcbufs, frame->cbufs);
|
||
BZ_Free(frame->cbufs);
|
||
vkDestroyFence(vk.device, frame->finishedfence, vkallocationcb);
|
||
Z_Free(frame);
|
||
}
|
||
|
||
if (vk.swapchain)
|
||
{
|
||
vkDestroySwapchainKHR(vk.device, vk.swapchain, vkallocationcb);
|
||
vk.swapchain = VK_NULL_HANDLE;
|
||
}
|
||
|
||
if (vk.backbufs)
|
||
free(vk.backbufs);
|
||
vk.backbufs = NULL;
|
||
vk.backbuf_count = 0;
|
||
}
|
||
|
||
static qboolean VK_CreateSwapChain(void)
|
||
{
|
||
qboolean reloadshaders = false;
|
||
uint32_t fmtcount;
|
||
VkSurfaceFormatKHR *surffmts;
|
||
uint32_t presentmodes;
|
||
VkPresentModeKHR *presentmode;
|
||
VkSurfaceCapabilitiesKHR surfcaps;
|
||
VkSwapchainCreateInfoKHR swapinfo = {VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR};
|
||
uint32_t i, curpri, preaquirecount;
|
||
VkSwapchainKHR newvkswapchain;
|
||
VkImage *images;
|
||
VkDeviceMemory *memories;
|
||
VkImageView attachments[3];
|
||
VkFramebufferCreateInfo fb_info = {VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO};
|
||
VkSampleCountFlagBits oldms;
|
||
uint32_t rpassflags = 0;
|
||
VkResult err;
|
||
|
||
VkFormat oldformat = vk.backbufformat;
|
||
VkFormat olddepthformat = vk.depthformat;
|
||
|
||
vk.dopresent(NULL); //make sure they're all pushed through.
|
||
|
||
|
||
vid_vsync.modified = false;
|
||
vid_triplebuffer.modified = false;
|
||
vid_srgb.modified = false;
|
||
vk_submissionthread.modified = false;
|
||
vk_waitfence.modified = false;
|
||
vid_multisample.modified = false;
|
||
|
||
vk.triplebuffer = vid_triplebuffer.ival;
|
||
vk.vsync = vid_vsync.ival;
|
||
|
||
if (!vk.khr_swapchain)
|
||
{ //headless
|
||
if (vk.swapchain || vk.backbuf_count)
|
||
VK_DestroySwapChain();
|
||
|
||
vk.backbufformat = ((vid.flags&VID_SRGBAWARE)||vid_srgb.ival)?VK_FORMAT_B8G8R8A8_SRGB:VK_FORMAT_B8G8R8A8_UNORM;
|
||
vk.backbuf_count = 4;
|
||
|
||
swapinfo.imageExtent.width = vid.pixelwidth;
|
||
swapinfo.imageExtent.height = vid.pixelheight;
|
||
|
||
images = malloc(sizeof(VkImage)*vk.backbuf_count);
|
||
memset(images, 0, sizeof(VkImage)*vk.backbuf_count);
|
||
memories = malloc(sizeof(VkDeviceMemory)*vk.backbuf_count);
|
||
memset(memories, 0, sizeof(VkDeviceMemory)*vk.backbuf_count);
|
||
|
||
vk.acquirelast = vk.acquirenext = 0;
|
||
for (i = 0; i < ACQUIRELIMIT; i++)
|
||
{
|
||
if (1)
|
||
{
|
||
VkFenceCreateInfo fci = {VK_STRUCTURE_TYPE_FENCE_CREATE_INFO};
|
||
fci.flags = VK_FENCE_CREATE_SIGNALED_BIT;
|
||
VkAssert(vkCreateFence(vk.device,&fci,vkallocationcb,&vk.acquirefences[i]));
|
||
vk.acquiresemaphores[i] = VK_NULL_HANDLE;
|
||
}
|
||
else
|
||
{
|
||
VkSemaphoreCreateInfo sci = {VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO};
|
||
VkAssert(vkCreateSemaphore(vk.device, &sci, vkallocationcb, &vk.acquiresemaphores[i]));
|
||
DebugSetName(VK_OBJECT_TYPE_SEMAPHORE, (uint64_t)vk.acquiresemaphores[i], "vk.acquiresemaphores");
|
||
vk.acquirefences[i] = VK_NULL_HANDLE;
|
||
}
|
||
|
||
vk.acquirebufferidx[vk.acquirelast%ACQUIRELIMIT] = vk.acquirelast%vk.backbuf_count;
|
||
vk.acquirelast++;
|
||
}
|
||
|
||
for (i = 0; i < vk.backbuf_count; i++)
|
||
{
|
||
VkMemoryRequirements mem_reqs;
|
||
VkMemoryAllocateInfo memAllocInfo = {VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO};
|
||
VkMemoryDedicatedAllocateInfoKHR khr_mdai = {VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO_KHR};
|
||
VkImageCreateInfo ici = {VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO};
|
||
|
||
ici.flags = 0;
|
||
ici.imageType = VK_IMAGE_TYPE_2D;
|
||
ici.format = vk.backbufformat;
|
||
ici.extent.width = vid.pixelwidth;
|
||
ici.extent.height = vid.pixelheight;
|
||
ici.extent.depth = 1;
|
||
ici.mipLevels = 1;
|
||
ici.arrayLayers = 1;
|
||
ici.samples = VK_SAMPLE_COUNT_1_BIT;
|
||
ici.tiling = VK_IMAGE_TILING_OPTIMAL;
|
||
ici.usage = VK_IMAGE_USAGE_SAMPLED_BIT|VK_IMAGE_USAGE_TRANSFER_SRC_BIT|VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
|
||
ici.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
||
ici.queueFamilyIndexCount = 0;
|
||
ici.pQueueFamilyIndices = NULL;
|
||
ici.initialLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
|
||
|
||
VkAssert(vkCreateImage(vk.device, &ici, vkallocationcb, &images[i]));
|
||
DebugSetName(VK_OBJECT_TYPE_IMAGE, (uint64_t)images[i], "backbuffer");
|
||
|
||
vkGetImageMemoryRequirements(vk.device, images[i], &mem_reqs);
|
||
|
||
memAllocInfo.allocationSize = mem_reqs.size;
|
||
memAllocInfo.memoryTypeIndex = vk_find_memory_try(mem_reqs.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT|VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
|
||
if (memAllocInfo.memoryTypeIndex == ~0)
|
||
memAllocInfo.memoryTypeIndex = vk_find_memory_try(mem_reqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
|
||
if (memAllocInfo.memoryTypeIndex == ~0)
|
||
memAllocInfo.memoryTypeIndex = vk_find_memory_try(mem_reqs.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT);
|
||
if (memAllocInfo.memoryTypeIndex == ~0)
|
||
memAllocInfo.memoryTypeIndex = vk_find_memory_require(mem_reqs.memoryTypeBits, 0);
|
||
|
||
if (vk.khr_dedicated_allocation)
|
||
{
|
||
khr_mdai.pNext = memAllocInfo.pNext;
|
||
khr_mdai.image = images[i];
|
||
memAllocInfo.pNext = &khr_mdai;
|
||
}
|
||
|
||
VkAssert(vkAllocateMemory(vk.device, &memAllocInfo, vkallocationcb, &memories[i]));
|
||
VkAssert(vkBindImageMemory(vk.device, images[i], memories[i], 0));
|
||
}
|
||
}
|
||
else
|
||
{ //using vulkan's presentation engine.
|
||
int BOOST_UNORM, BOOST_SNORM, BOOST_SRGB, BOOST_UFLOAT, BOOST_SFLOAT;
|
||
|
||
if (vid_srgb.ival > 1)
|
||
{ //favour float formats, then srgb, then unorms
|
||
BOOST_UNORM = 0;
|
||
BOOST_SNORM = 0;
|
||
BOOST_SRGB = 128;
|
||
BOOST_UFLOAT = 256;
|
||
BOOST_SFLOAT = 256;
|
||
}
|
||
else if (vid_srgb.ival)
|
||
{
|
||
BOOST_UNORM = 0;
|
||
BOOST_SNORM = 0;
|
||
BOOST_SRGB = 256;
|
||
BOOST_UFLOAT = 128;
|
||
BOOST_SFLOAT = 128;
|
||
}
|
||
else
|
||
{
|
||
BOOST_UNORM = 256;
|
||
BOOST_SNORM = 256;
|
||
BOOST_SRGB = 0;
|
||
BOOST_UFLOAT = 128;
|
||
BOOST_SFLOAT = 128;
|
||
}
|
||
|
||
VkAssert(vkGetPhysicalDeviceSurfaceFormatsKHR(vk.gpu, vk.surface, &fmtcount, NULL));
|
||
surffmts = malloc(sizeof(VkSurfaceFormatKHR)*fmtcount);
|
||
VkAssert(vkGetPhysicalDeviceSurfaceFormatsKHR(vk.gpu, vk.surface, &fmtcount, surffmts));
|
||
|
||
VkAssert(vkGetPhysicalDeviceSurfacePresentModesKHR(vk.gpu, vk.surface, &presentmodes, NULL));
|
||
presentmode = malloc(sizeof(VkPresentModeKHR)*presentmodes);
|
||
VkAssert(vkGetPhysicalDeviceSurfacePresentModesKHR(vk.gpu, vk.surface, &presentmodes, presentmode));
|
||
|
||
vkGetPhysicalDeviceSurfaceCapabilitiesKHR(vk.gpu, vk.surface, &surfcaps);
|
||
|
||
swapinfo.surface = vk.surface;
|
||
swapinfo.minImageCount = surfcaps.minImageCount+vk.triplebuffer;
|
||
if (swapinfo.minImageCount > surfcaps.maxImageCount)
|
||
swapinfo.minImageCount = surfcaps.maxImageCount;
|
||
if (swapinfo.minImageCount < surfcaps.minImageCount)
|
||
swapinfo.minImageCount = surfcaps.minImageCount;
|
||
swapinfo.imageExtent.width = surfcaps.currentExtent.width;
|
||
swapinfo.imageExtent.height = surfcaps.currentExtent.height;
|
||
swapinfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT|VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
|
||
swapinfo.preTransform = surfcaps.currentTransform;//VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;
|
||
if (surfcaps.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR)
|
||
swapinfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
|
||
else if (surfcaps.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR)
|
||
{
|
||
swapinfo.compositeAlpha = VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR;
|
||
Con_Printf(CON_WARNING"Vulkan swapchain using composite alpha premultiplied\n");
|
||
}
|
||
else if (surfcaps.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_POST_MULTIPLIED_BIT_KHR)
|
||
{
|
||
swapinfo.compositeAlpha = VK_COMPOSITE_ALPHA_POST_MULTIPLIED_BIT_KHR;
|
||
Con_Printf(CON_WARNING"Vulkan swapchain using composite alpha postmultiplied\n");
|
||
}
|
||
else
|
||
{
|
||
swapinfo.compositeAlpha = VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR; //erk?
|
||
Con_Printf(CON_WARNING"composite alpha inherit\n");
|
||
}
|
||
swapinfo.imageArrayLayers = /*(r_stereo_method.ival==1)?2:*/1;
|
||
swapinfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
||
swapinfo.queueFamilyIndexCount = 0;
|
||
swapinfo.pQueueFamilyIndices = NULL;
|
||
swapinfo.clipped = vid_isfullscreen?VK_FALSE:VK_TRUE; //allow fragment shaders to be skipped on parts that are obscured by another window. screenshots might get weird, so use proper captures if required/automagic.
|
||
|
||
swapinfo.presentMode = VK_PRESENT_MODE_FIFO_KHR; //support is guarenteed by spec, in theory.
|
||
for (i = 0, curpri = 0; i < presentmodes; i++)
|
||
{
|
||
uint32_t priority = 0;
|
||
switch(presentmode[i])
|
||
{
|
||
default://ignore it if we don't know it.
|
||
break;
|
||
//this is awkward. normally we use vsync<0 to allow tearing-with-vsync, but that leaves us with a problem as far as what 0 should signify - tearing or not.
|
||
//if we're using mailbox then we could instead discard the command buffers and skip rendering of the actual scenes.
|
||
//we could have our submission thread wait some time period after the last vswap (ie: before the next) before submitting the command.
|
||
//this could reduce gpu load at higher resolutions without lying too much about cpu usage...
|
||
case VK_PRESENT_MODE_IMMEDIATE_KHR:
|
||
priority = (vk.vsync?0:2) + 2; //for most quake players, latency trumps tearing.
|
||
break;
|
||
case VK_PRESENT_MODE_MAILBOX_KHR:
|
||
priority = (vk.vsync?0:2) + 1;
|
||
break;
|
||
case VK_PRESENT_MODE_FIFO_KHR:
|
||
priority = (vk.vsync?2:0) + 1;
|
||
break;
|
||
case VK_PRESENT_MODE_FIFO_RELAXED_KHR:
|
||
priority = (vk.vsync?2:0) + 2; //strict vsync results in weird juddering if rtlights etc caues framerates to drop below the refreshrate. and nvidia just suck with vsync, so I'm not taking any chances.
|
||
break;
|
||
}
|
||
if (priority > curpri)
|
||
{
|
||
curpri = priority;
|
||
swapinfo.presentMode = presentmode[i];
|
||
}
|
||
}
|
||
|
||
if (!vk.vsync && swapinfo.presentMode != VK_PRESENT_MODE_IMMEDIATE_KHR)
|
||
if (!vk.swapchain) //only warn on vid_restart, otherwise its annoying when resizing.
|
||
Con_Printf("Warning: vulkan graphics driver does not support VK_PRESENT_MODE_IMMEDIATE_KHR.\n");
|
||
|
||
vk.srgbcapable = false;
|
||
swapinfo.imageColorSpace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR;
|
||
swapinfo.imageFormat = VK_FORMAT_UNDEFINED;
|
||
for (i = 0, curpri = 0; i < fmtcount; i++)
|
||
{
|
||
uint32_t priority = 0;
|
||
|
||
switch(surffmts[i].format)
|
||
{
|
||
case VK_FORMAT_B8G8R8A8_UNORM:
|
||
case VK_FORMAT_R8G8B8A8_UNORM:
|
||
case VK_FORMAT_A8B8G8R8_UNORM_PACK32:
|
||
priority = ((vid_bpp.ival>=24)?24:11)+BOOST_UNORM;
|
||
break;
|
||
case VK_FORMAT_B8G8R8A8_SNORM:
|
||
case VK_FORMAT_R8G8B8A8_SNORM:
|
||
case VK_FORMAT_A8B8G8R8_SNORM_PACK32:
|
||
priority = ((vid_bpp.ival>=21)?21:2)+BOOST_SNORM;
|
||
break;
|
||
case VK_FORMAT_B8G8R8A8_SRGB:
|
||
case VK_FORMAT_R8G8B8A8_SRGB:
|
||
case VK_FORMAT_A8B8G8R8_SRGB_PACK32:
|
||
priority = ((vid_bpp.ival>=24)?24:11)+BOOST_SRGB;
|
||
vk.srgbcapable = true;
|
||
break;
|
||
case VK_FORMAT_A2B10G10R10_UNORM_PACK32:
|
||
case VK_FORMAT_A2R10G10B10_UNORM_PACK32:
|
||
priority = ((vid_bpp.ival==30)?30:10)+BOOST_UNORM;
|
||
break;
|
||
|
||
case VK_FORMAT_B10G11R11_UFLOAT_PACK32:
|
||
priority = ((vid_srgb.ival>=3||vid_bpp.ival==32)?32:11)+BOOST_UFLOAT;
|
||
break;
|
||
case VK_FORMAT_R16G16B16A16_SFLOAT: //16bit per-channel formats
|
||
priority = ((vid_srgb.ival>=3||vid_bpp.ival>=48)?48:9)+BOOST_SFLOAT;
|
||
break;
|
||
case VK_FORMAT_R16G16B16A16_UNORM:
|
||
priority = ((vid_srgb.ival>=3||vid_bpp.ival>=48)?48:9)+BOOST_UNORM;
|
||
break;
|
||
case VK_FORMAT_R16G16B16A16_SNORM:
|
||
priority = ((vid_srgb.ival>=3||vid_bpp.ival>=48)?48:9)+BOOST_SFLOAT;
|
||
break;
|
||
case VK_FORMAT_R32G32B32A32_SFLOAT: //32bit per-channel formats
|
||
priority = ((vid_bpp.ival>=47)?96:8)+BOOST_SFLOAT;
|
||
break;
|
||
|
||
case VK_FORMAT_B5G6R5_UNORM_PACK16:
|
||
case VK_FORMAT_R5G6B5_UNORM_PACK16:
|
||
priority = 16+BOOST_UNORM;
|
||
break;
|
||
case VK_FORMAT_R4G4B4A4_UNORM_PACK16:
|
||
case VK_FORMAT_B4G4R4A4_UNORM_PACK16:
|
||
priority = 12+BOOST_UNORM;
|
||
break;
|
||
case VK_FORMAT_A1R5G5B5_UNORM_PACK16:
|
||
case VK_FORMAT_R5G5B5A1_UNORM_PACK16:
|
||
case VK_FORMAT_B5G5R5A1_UNORM_PACK16:
|
||
priority = 15+BOOST_UNORM;
|
||
break;
|
||
|
||
default: //no idea, use as lowest priority.
|
||
priority = 1;
|
||
break;
|
||
}
|
||
|
||
if (surffmts[i].colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR && //sRGB
|
||
surffmts[i].colorSpace == VK_COLOR_SPACE_EXTENDED_SRGB_NONLINEAR_EXT && //scRGB
|
||
surffmts[i].colorSpace == VK_COLOR_SPACE_EXTENDED_SRGB_LINEAR_EXT) //linear vaugely like sRGB
|
||
priority += 512; //always favour supported colour spaces.
|
||
|
||
if (priority > curpri)
|
||
{
|
||
curpri = priority;
|
||
swapinfo.imageColorSpace = surffmts[i].colorSpace;
|
||
swapinfo.imageFormat = surffmts[i].format;
|
||
}
|
||
}
|
||
|
||
if (swapinfo.imageFormat == VK_FORMAT_UNDEFINED)
|
||
{ //if we found this format then it means the drivers don't really give a damn. pick a real format.
|
||
if (vid_srgb.ival > 1 && swapinfo.imageColorSpace == VK_COLOR_SPACE_EXTENDED_SRGB_LINEAR_EXT)
|
||
swapinfo.imageFormat = VK_FORMAT_R16G16B16A16_SFLOAT;
|
||
else if (vid_srgb.ival)
|
||
swapinfo.imageFormat = VK_FORMAT_R8G8B8A8_SRGB;
|
||
else
|
||
swapinfo.imageFormat = VK_FORMAT_R8G8B8A8_UNORM;
|
||
}
|
||
|
||
if (vk.backbufformat != swapinfo.imageFormat)
|
||
{
|
||
VK_DestroyRenderPasses();
|
||
reloadshaders = true;
|
||
}
|
||
vk.backbufformat = swapinfo.imageFormat;
|
||
|
||
//VK_COLORSPACE_SRGB_NONLINEAR means the presentation engine will interpret the image as SRGB whether its a UNORM or SRGB format or not.
|
||
//an SRGB format JUST means rendering converts linear->srgb and does not apply to the presentation engine.
|
||
vid.flags &= ~VID_SRGB_FB;
|
||
if (swapinfo.imageColorSpace == VK_COLOR_SPACE_EXTENDED_SRGB_LINEAR_EXT)
|
||
vid.flags |= VID_SRGB_FB_LINEAR;
|
||
else
|
||
{
|
||
switch(vk.backbufformat)
|
||
{
|
||
case VK_FORMAT_R8G8B8_SRGB:
|
||
case VK_FORMAT_B8G8R8_SRGB:
|
||
case VK_FORMAT_B8G8R8A8_SRGB:
|
||
case VK_FORMAT_R8G8B8A8_SRGB:
|
||
case VK_FORMAT_A8B8G8R8_SRGB_PACK32:
|
||
vid.flags |= VID_SRGB_FB_LINEAR;
|
||
break;
|
||
default:
|
||
break; //non-srgb (or compressed)
|
||
}
|
||
}
|
||
|
||
free(presentmode);
|
||
free(surffmts);
|
||
|
||
if (vid_isfullscreen) //nvidia really doesn't like this. its fine when windowed though.
|
||
VK_DestroySwapChain();
|
||
swapinfo.oldSwapchain = vk.swapchain;
|
||
|
||
newvkswapchain = VK_NULL_HANDLE;
|
||
err = vkCreateSwapchainKHR(vk.device, &swapinfo, vkallocationcb, &newvkswapchain);
|
||
switch(err)
|
||
{
|
||
case VK_SUCCESS:
|
||
break;
|
||
default:
|
||
Sys_Error("vkCreateSwapchainKHR returned undocumented error!\n");
|
||
case VK_ERROR_OUT_OF_HOST_MEMORY:
|
||
case VK_ERROR_OUT_OF_DEVICE_MEMORY:
|
||
case VK_ERROR_DEVICE_LOST:
|
||
case VK_ERROR_SURFACE_LOST_KHR:
|
||
case VK_ERROR_NATIVE_WINDOW_IN_USE_KHR:
|
||
case VK_ERROR_INITIALIZATION_FAILED:
|
||
if (swapinfo.oldSwapchain)
|
||
Con_Printf(CON_WARNING"vkCreateSwapchainKHR(%u * %u) failed with error %s\n", swapinfo.imageExtent.width, swapinfo.imageExtent.height, VK_VKErrorToString(err));
|
||
else
|
||
Sys_Error("vkCreateSwapchainKHR(%u * %u) failed with error %s\n", swapinfo.imageExtent.width, swapinfo.imageExtent.height, VK_VKErrorToString(err));
|
||
VK_DestroySwapChain();
|
||
return false;
|
||
}
|
||
if (!newvkswapchain)
|
||
return false;
|
||
if (vk.swapchain)
|
||
{
|
||
VK_DestroySwapChain();
|
||
}
|
||
vk.swapchain = newvkswapchain;
|
||
|
||
VkAssert(vkGetSwapchainImagesKHR(vk.device, vk.swapchain, &vk.backbuf_count, NULL));
|
||
images = malloc(sizeof(VkImage)*vk.backbuf_count);
|
||
memories = NULL;
|
||
VkAssert(vkGetSwapchainImagesKHR(vk.device, vk.swapchain, &vk.backbuf_count, images));
|
||
|
||
vk.acquirelast = vk.acquirenext = 0;
|
||
for (i = 0; i < ACQUIRELIMIT; i++)
|
||
{
|
||
if (vk_waitfence.ival || !*vk_waitfence.string)
|
||
{
|
||
VkFenceCreateInfo fci = {VK_STRUCTURE_TYPE_FENCE_CREATE_INFO};
|
||
VkAssert(vkCreateFence(vk.device,&fci,vkallocationcb,&vk.acquirefences[i]));
|
||
vk.acquiresemaphores[i] = VK_NULL_HANDLE;
|
||
}
|
||
else
|
||
{
|
||
VkSemaphoreCreateInfo sci = {VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO};
|
||
VkAssert(vkCreateSemaphore(vk.device, &sci, vkallocationcb, &vk.acquiresemaphores[i]));
|
||
DebugSetName(VK_OBJECT_TYPE_SEMAPHORE, (uint64_t)vk.acquiresemaphores[i], "vk.acquiresemaphores");
|
||
vk.acquirefences[i] = VK_NULL_HANDLE;
|
||
}
|
||
}
|
||
if (!vk_submissionthread.value && *vk_submissionthread.string)
|
||
preaquirecount = 1; //no real point asking for more.
|
||
else
|
||
preaquirecount = vk.backbuf_count;
|
||
/*-1 to hide any weird thread issues*/
|
||
while (vk.acquirelast < ACQUIRELIMIT-1 && vk.acquirelast < preaquirecount && vk.acquirelast <= vk.backbuf_count-surfcaps.minImageCount)
|
||
{
|
||
VkAssert(vkAcquireNextImageKHR(vk.device, vk.swapchain, UINT64_MAX, vk.acquiresemaphores[vk.acquirelast%ACQUIRELIMIT], vk.acquirefences[vk.acquirelast%ACQUIRELIMIT], &vk.acquirebufferidx[vk.acquirelast%ACQUIRELIMIT]));
|
||
vk.acquirelast++;
|
||
}
|
||
}
|
||
|
||
oldms = vk.multisamplebits;
|
||
|
||
vk.multisamplebits = VK_SAMPLE_COUNT_1_BIT;
|
||
if (vid_multisample.ival>1)
|
||
{
|
||
VkSampleCountFlags fl = vk.limits.framebufferColorSampleCounts & vk.limits.framebufferDepthSampleCounts;
|
||
// Con_Printf("Warning: vulkan multisample does not work with rtlights or render targets etc etc\n");
|
||
for (i = 1; i < 30; i++)
|
||
if ((fl & (1<<i)) && (1<<i) <= vid_multisample.ival)
|
||
vk.multisamplebits = (1<<i);
|
||
}
|
||
|
||
rpassflags = RP_PRESENTABLE;
|
||
|
||
//destroy+recreate the renderpass if something changed that prevents them being compatible (this also requires rebuilding all the pipelines too, which sucks).
|
||
if (oldms != vk.multisamplebits || oldformat != vk.backbufformat || olddepthformat != vk.depthformat)
|
||
{
|
||
VK_DestroyRenderPasses();
|
||
reloadshaders = true;
|
||
}
|
||
|
||
if (reloadshaders)
|
||
{
|
||
Shader_NeedReload(true);
|
||
Shader_DoReload();
|
||
}
|
||
|
||
attachments[0] = VK_NULL_HANDLE; //colour
|
||
attachments[1] = VK_NULL_HANDLE; //depth
|
||
attachments[2] = VK_NULL_HANDLE; //mscolour
|
||
|
||
if (rpassflags & RP_MULTISAMPLE)
|
||
fb_info.attachmentCount = 3;
|
||
else
|
||
{
|
||
rpassflags &= ~RP_PRESENTABLE;
|
||
fb_info.attachmentCount = 2;
|
||
}
|
||
fb_info.renderPass = VK_GetRenderPass(RP_FULLCLEAR|rpassflags);
|
||
fb_info.pAttachments = attachments;
|
||
fb_info.width = swapinfo.imageExtent.width;
|
||
fb_info.height = swapinfo.imageExtent.height;
|
||
fb_info.layers = 1;
|
||
|
||
|
||
vk.backbufs = malloc(sizeof(*vk.backbufs)*vk.backbuf_count);
|
||
memset(vk.backbufs, 0, sizeof(*vk.backbufs)*vk.backbuf_count);
|
||
for (i = 0; i < vk.backbuf_count; i++)
|
||
{
|
||
VkImageViewCreateInfo ivci = {VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO};
|
||
|
||
vk.backbufs[i].colour.image = images[i];
|
||
DebugSetName(VK_OBJECT_TYPE_IMAGE, (uint64_t)vk.backbufs[i].colour.image, "backbuffer");
|
||
|
||
ivci.format = vk.backbufformat;
|
||
// ivci.components.r = VK_COMPONENT_SWIZZLE_R;
|
||
// ivci.components.g = VK_COMPONENT_SWIZZLE_G;
|
||
// ivci.components.b = VK_COMPONENT_SWIZZLE_B;
|
||
// ivci.components.a = VK_COMPONENT_SWIZZLE_A;
|
||
ivci.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||
ivci.subresourceRange.baseMipLevel = 0;
|
||
ivci.subresourceRange.levelCount = 1;
|
||
ivci.subresourceRange.baseArrayLayer = 0;
|
||
ivci.subresourceRange.layerCount = 1;
|
||
ivci.viewType = VK_IMAGE_VIEW_TYPE_2D;
|
||
ivci.flags = 0;
|
||
ivci.image = images[i];
|
||
if (memories)
|
||
vk.backbufs[i].colour.mem.memory = memories[i];
|
||
vk.backbufs[i].colour.width = swapinfo.imageExtent.width;
|
||
vk.backbufs[i].colour.height = swapinfo.imageExtent.height;
|
||
VkAssert(vkCreateImageView(vk.device, &ivci, vkallocationcb, &vk.backbufs[i].colour.view));
|
||
|
||
vk.backbufs[i].firstuse = true;
|
||
|
||
//create the depth buffer texture. possibly multisampled.
|
||
{
|
||
//depth image
|
||
{
|
||
VkImageCreateInfo depthinfo = {VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO};
|
||
depthinfo.flags = 0;
|
||
depthinfo.imageType = VK_IMAGE_TYPE_2D;
|
||
depthinfo.format = vk.depthformat;
|
||
depthinfo.extent.width = swapinfo.imageExtent.width;
|
||
depthinfo.extent.height = swapinfo.imageExtent.height;
|
||
depthinfo.extent.depth = 1;
|
||
depthinfo.mipLevels = 1;
|
||
depthinfo.arrayLayers = 1;
|
||
depthinfo.samples = (rpassflags & RP_MULTISAMPLE)?vk.multisamplebits:VK_SAMPLE_COUNT_1_BIT;
|
||
depthinfo.tiling = VK_IMAGE_TILING_OPTIMAL;
|
||
depthinfo.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
|
||
depthinfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
||
depthinfo.queueFamilyIndexCount = 0;
|
||
depthinfo.pQueueFamilyIndices = NULL;
|
||
depthinfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||
VkAssert(vkCreateImage(vk.device, &depthinfo, vkallocationcb, &vk.backbufs[i].depth.image));
|
||
DebugSetName(VK_OBJECT_TYPE_IMAGE, (uint64_t)vk.backbufs[i].depth.image, "backbuffer depth");
|
||
}
|
||
|
||
//depth memory
|
||
VK_AllocateBindImageMemory(&vk.backbufs[i].depth, true);
|
||
|
||
//depth view
|
||
{
|
||
VkImageViewCreateInfo depthviewinfo = {VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO};
|
||
depthviewinfo.format = vk.depthformat;
|
||
depthviewinfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY;
|
||
depthviewinfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY;
|
||
depthviewinfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY;
|
||
depthviewinfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY;
|
||
depthviewinfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT;//|VK_IMAGE_ASPECT_STENCIL_BIT;
|
||
depthviewinfo.subresourceRange.baseMipLevel = 0;
|
||
depthviewinfo.subresourceRange.levelCount = 1;
|
||
depthviewinfo.subresourceRange.baseArrayLayer = 0;
|
||
depthviewinfo.subresourceRange.layerCount = 1;
|
||
depthviewinfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
|
||
depthviewinfo.flags = 0;
|
||
depthviewinfo.image = vk.backbufs[i].depth.image;
|
||
VkAssert(vkCreateImageView(vk.device, &depthviewinfo, vkallocationcb, &vk.backbufs[i].depth.view));
|
||
attachments[1] = vk.backbufs[i].depth.view;
|
||
}
|
||
}
|
||
|
||
//if we're using multisampling, create the intermediate multisample texture that we're actually going to render to.
|
||
if (rpassflags & RP_MULTISAMPLE)
|
||
{
|
||
//mscolour image
|
||
{
|
||
VkImageCreateInfo mscolourinfo = {VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO};
|
||
mscolourinfo.flags = 0;
|
||
mscolourinfo.imageType = VK_IMAGE_TYPE_2D;
|
||
mscolourinfo.format = vk.backbufformat;
|
||
mscolourinfo.extent.width = swapinfo.imageExtent.width;
|
||
mscolourinfo.extent.height = swapinfo.imageExtent.height;
|
||
mscolourinfo.extent.depth = 1;
|
||
mscolourinfo.mipLevels = 1;
|
||
mscolourinfo.arrayLayers = 1;
|
||
mscolourinfo.samples = vk.multisamplebits;
|
||
mscolourinfo.tiling = VK_IMAGE_TILING_OPTIMAL;
|
||
mscolourinfo.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
|
||
mscolourinfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
||
mscolourinfo.queueFamilyIndexCount = 0;
|
||
mscolourinfo.pQueueFamilyIndices = NULL;
|
||
mscolourinfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||
VkAssert(vkCreateImage(vk.device, &mscolourinfo, vkallocationcb, &vk.backbufs[i].mscolour.image));
|
||
DebugSetName(VK_OBJECT_TYPE_IMAGE, (uint64_t)vk.backbufs[i].mscolour.image, "multisample");
|
||
}
|
||
|
||
//mscolour memory
|
||
VK_AllocateBindImageMemory(&vk.backbufs[i].mscolour, true);
|
||
|
||
//mscolour view
|
||
{
|
||
VkImageViewCreateInfo mscolourviewinfo = {VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO};
|
||
mscolourviewinfo.format = vk.backbufformat;
|
||
mscolourviewinfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY;
|
||
mscolourviewinfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY;
|
||
mscolourviewinfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY;
|
||
mscolourviewinfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY;
|
||
mscolourviewinfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||
mscolourviewinfo.subresourceRange.baseMipLevel = 0;
|
||
mscolourviewinfo.subresourceRange.levelCount = 1;
|
||
mscolourviewinfo.subresourceRange.baseArrayLayer = 0;
|
||
mscolourviewinfo.subresourceRange.layerCount = 1;
|
||
mscolourviewinfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
|
||
mscolourviewinfo.flags = 0;
|
||
mscolourviewinfo.image = vk.backbufs[i].mscolour.image;
|
||
VkAssert(vkCreateImageView(vk.device, &mscolourviewinfo, vkallocationcb, &vk.backbufs[i].mscolour.view));
|
||
attachments[2] = vk.backbufs[i].mscolour.view;
|
||
}
|
||
}
|
||
|
||
|
||
attachments[0] = vk.backbufs[i].colour.view;
|
||
VkAssert(vkCreateFramebuffer(vk.device, &fb_info, vkallocationcb, &vk.backbufs[i].framebuffer));
|
||
|
||
{
|
||
VkSemaphoreCreateInfo seminfo = {VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO};
|
||
VkAssert(vkCreateSemaphore(vk.device, &seminfo, vkallocationcb, &vk.backbufs[i].presentsemaphore));
|
||
DebugSetName(VK_OBJECT_TYPE_SEMAPHORE, (uint64_t)vk.backbufs[i].presentsemaphore, "vk.backbufs.presentsemaphore");
|
||
}
|
||
}
|
||
free(images);
|
||
free(memories);
|
||
|
||
vid.pixelwidth = swapinfo.imageExtent.width;
|
||
vid.pixelheight = swapinfo.imageExtent.height;
|
||
R2D_Console_Resize();
|
||
|
||
return true;
|
||
}
|
||
|
||
|
||
void VK_Draw_Init(void)
|
||
{
|
||
R2D_Init();
|
||
}
|
||
void VK_Draw_Shutdown(void)
|
||
{
|
||
R2D_Shutdown();
|
||
Shader_Shutdown();
|
||
Image_Shutdown();
|
||
}
|
||
|
||
static void VK_DestroySampler(VkSampler s)
|
||
{
|
||
struct vksamplers_s *ref;
|
||
for (ref = vk.samplers; ref; ref = ref->next)
|
||
{
|
||
if (ref->samp == s)
|
||
{
|
||
if (--ref->usages == 0)
|
||
{
|
||
vkDestroySampler(vk.device, ref->samp, vkallocationcb);
|
||
*ref->link = ref->next;
|
||
if (ref->next)
|
||
ref->next->link = ref->link;
|
||
Z_Free(ref);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
static void VK_DestroySampler_FrameEnd(void *w)
|
||
{
|
||
VK_DestroySampler(*(VkSampler*)w);
|
||
}
|
||
|
||
void VK_CreateSamplerInfo(VkSamplerCreateInfo *info, vk_image_t *img)
|
||
{
|
||
unsigned int flags = IF_RENDERTARGET;
|
||
struct vksamplers_s *ref;
|
||
|
||
if (img->sampler)
|
||
VK_DestroySampler(img->sampler);
|
||
|
||
|
||
for (ref = vk.samplers; ref; ref = ref->next)
|
||
if (ref->flags == flags)
|
||
if (!memcmp(&ref->props, info, sizeof(*info)))
|
||
break;
|
||
|
||
if (!ref)
|
||
{
|
||
ref = Z_Malloc(sizeof(*ref));
|
||
ref->flags = flags;
|
||
ref->props = *info;
|
||
ref->next = vk.samplers;
|
||
ref->link = &vk.samplers;
|
||
if (vk.samplers)
|
||
vk.samplers->link = &ref->next;
|
||
vk.samplers = ref;
|
||
VkAssert(vkCreateSampler(vk.device, &ref->props, NULL, &ref->samp));
|
||
}
|
||
ref->usages++;
|
||
img->sampler = ref->samp;
|
||
}
|
||
void VK_CreateSampler(unsigned int flags, vk_image_t *img)
|
||
{
|
||
struct vksamplers_s *ref;
|
||
qboolean clamptoedge = flags & IF_CLAMP;
|
||
VkSamplerCreateInfo lmsampinfo = {VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO};
|
||
|
||
|
||
if (img->sampler)
|
||
VK_DestroySampler(img->sampler);
|
||
|
||
if (flags & IF_LINEAR)
|
||
{
|
||
lmsampinfo.minFilter = lmsampinfo.magFilter = VK_FILTER_LINEAR;
|
||
lmsampinfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
|
||
}
|
||
else if (flags & IF_NEAREST)
|
||
{
|
||
lmsampinfo.minFilter = lmsampinfo.magFilter = VK_FILTER_NEAREST;
|
||
lmsampinfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST;
|
||
}
|
||
else
|
||
{
|
||
int *filter = (flags & IF_UIPIC)?vk.filterpic:vk.filtermip;
|
||
if (filter[0])
|
||
lmsampinfo.minFilter = VK_FILTER_LINEAR;
|
||
else
|
||
lmsampinfo.minFilter = VK_FILTER_NEAREST;
|
||
if (filter[1])
|
||
lmsampinfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
|
||
else
|
||
lmsampinfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST;
|
||
if (filter[2])
|
||
lmsampinfo.magFilter = VK_FILTER_LINEAR;
|
||
else
|
||
lmsampinfo.magFilter = VK_FILTER_NEAREST;
|
||
}
|
||
|
||
lmsampinfo.addressModeU = clamptoedge?VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE:VK_SAMPLER_ADDRESS_MODE_REPEAT;
|
||
lmsampinfo.addressModeV = clamptoedge?VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE:VK_SAMPLER_ADDRESS_MODE_REPEAT;
|
||
lmsampinfo.addressModeW = clamptoedge?VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE:VK_SAMPLER_ADDRESS_MODE_REPEAT;
|
||
lmsampinfo.mipLodBias = vk.lodbias;
|
||
lmsampinfo.anisotropyEnable = (flags & IF_NEAREST)?false:(vk.max_anistophy > 1);
|
||
lmsampinfo.maxAnisotropy = vk.max_anistophy;
|
||
lmsampinfo.compareEnable = VK_FALSE;
|
||
lmsampinfo.compareOp = VK_COMPARE_OP_NEVER;
|
||
lmsampinfo.minLod = vk.mipcap[0]; //this isn't quite right
|
||
lmsampinfo.maxLod = vk.mipcap[1];
|
||
lmsampinfo.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_BLACK;
|
||
lmsampinfo.unnormalizedCoordinates = VK_FALSE;
|
||
|
||
for (ref = vk.samplers; ref; ref = ref->next)
|
||
if (ref->flags == flags)
|
||
if (!memcmp(&ref->props, &lmsampinfo, sizeof(lmsampinfo)))
|
||
break;
|
||
|
||
if (!ref)
|
||
{
|
||
ref = Z_Malloc(sizeof(*ref));
|
||
ref->flags = flags;
|
||
ref->props = lmsampinfo;
|
||
ref->next = vk.samplers;
|
||
ref->link = &vk.samplers;
|
||
if (vk.samplers)
|
||
vk.samplers->link = &ref->next;
|
||
vk.samplers = ref;
|
||
VkAssert(vkCreateSampler(vk.device, &ref->props, NULL, &ref->samp));
|
||
}
|
||
ref->usages++;
|
||
img->sampler = ref->samp;
|
||
}
|
||
|
||
void VK_UpdateFiltering(image_t *imagelist, int filtermip[3], int filterpic[3], int mipcap[2], float lodbias, float anis)
|
||
{
|
||
uint32_t i;
|
||
for (i = 0; i < countof(vk.filtermip); i++)
|
||
vk.filtermip[i] = filtermip[i];
|
||
for (i = 0; i < countof(vk.filterpic); i++)
|
||
vk.filterpic[i] = filterpic[i];
|
||
for (i = 0; i < countof(vk.mipcap); i++)
|
||
vk.mipcap[i] = mipcap[i];
|
||
vk.lodbias = lodbias;
|
||
vk.max_anistophy = bound(1.0, anis, vk.limits.maxSamplerAnisotropy);
|
||
|
||
while(imagelist)
|
||
{
|
||
if (imagelist->vkimage)
|
||
{
|
||
if (imagelist->vkimage->sampler)
|
||
{ //the sampler might still be in use, so clean it up at the end of the frame.
|
||
//all this to avoid syncing all the queues...
|
||
VK_AtFrameEnd(VK_DestroySampler_FrameEnd, &imagelist->vkimage->sampler, sizeof(imagelist->vkimage->sampler));
|
||
imagelist->vkimage->sampler = VK_NULL_HANDLE;
|
||
}
|
||
VK_CreateSampler(imagelist->flags, imagelist->vkimage);
|
||
}
|
||
imagelist = imagelist->next;
|
||
}
|
||
}
|
||
|
||
qboolean VK_AllocatePoolMemory(uint32_t pooltype, VkDeviceSize memsize, VkDeviceSize poolalignment, vk_poolmem_t *mem)
|
||
{
|
||
struct vk_mempool_s *p;
|
||
VkDeviceSize pad;
|
||
|
||
if (!vk_usememorypools.ival)
|
||
return false;
|
||
|
||
// if (memsize > 1024*1024*4)
|
||
// return false;
|
||
for (p = vk.mempools; p; p = p->next)
|
||
{
|
||
if (p->memtype == pooltype)
|
||
{
|
||
if (p->memoryoffset + poolalignment + memsize < p->memorysize)
|
||
break;
|
||
}
|
||
}
|
||
if (!p)
|
||
{
|
||
VkMemoryAllocateInfo poolai = {VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO};
|
||
p = Z_Malloc(sizeof(*p));
|
||
p->memorysize = poolai.allocationSize = 512*1024*1024; //lets just allocate big...
|
||
p->memtype = poolai.memoryTypeIndex = pooltype;
|
||
|
||
if (VK_SUCCESS != vkAllocateMemory(vk.device, &poolai, vkallocationcb, &p->memory))
|
||
{ //out of memory? oh well, a smaller dedicated allocation might still work.
|
||
Z_Free(p);
|
||
return false;
|
||
}
|
||
p->next = vk.mempools;
|
||
vk.mempools = p;
|
||
}
|
||
pad = ((p->memoryoffset+poolalignment-1)&~(poolalignment-1)) - p->memoryoffset;
|
||
p->memoryoffset = (p->memoryoffset+poolalignment-1)&~(poolalignment-1);
|
||
p->gaps += pad;
|
||
mem->offset = p->memoryoffset;
|
||
mem->size = memsize; //FIXME: we have no way to deal with gaps due to alignment
|
||
mem->memory = p->memory;
|
||
mem->pool = p;
|
||
|
||
p->memoryoffset += memsize;
|
||
return true;
|
||
}
|
||
void VK_ReleasePoolMemory(vk_poolmem_t *mem)
|
||
{
|
||
if (mem->pool)
|
||
{
|
||
//FIXME: track power-of-two holes?
|
||
mem->pool->gaps += mem->size;
|
||
mem->pool = NULL;
|
||
mem->memory = VK_NULL_HANDLE;
|
||
}
|
||
else if (mem->memory)
|
||
{
|
||
vkFreeMemory(vk.device, mem->memory, vkallocationcb);
|
||
mem->memory = VK_NULL_HANDLE;
|
||
}
|
||
}
|
||
|
||
|
||
//does NOT bind.
|
||
//image memory is NOT expected to be host-visible. you'll get what vulkan gives you.
|
||
qboolean VK_AllocateImageMemory(VkImage image, qboolean dedicated, vk_poolmem_t *mem)
|
||
{
|
||
uint32_t pooltype;
|
||
VkMemoryRequirements2KHR mem_reqs2 = {VK_STRUCTURE_TYPE_MEMORY_REQUIREMENTS_2_KHR};
|
||
|
||
if (!dedicated && vk.khr_get_memory_requirements2)
|
||
{
|
||
VkImageMemoryRequirementsInfo2KHR imri = {VK_STRUCTURE_TYPE_IMAGE_MEMORY_REQUIREMENTS_INFO_2_KHR};
|
||
VkMemoryDedicatedRequirementsKHR mdr = {VK_STRUCTURE_TYPE_MEMORY_DEDICATED_REQUIREMENTS_KHR};
|
||
imri.image = image;
|
||
if (vk.khr_dedicated_allocation)
|
||
mem_reqs2.pNext = &mdr; //chain the result struct
|
||
vkGetImageMemoryRequirements2KHR(vk.device, &imri, &mem_reqs2);
|
||
|
||
//and now we know if it should be dedicated or not.
|
||
dedicated |= mdr.prefersDedicatedAllocation || mdr.requiresDedicatedAllocation;
|
||
}
|
||
else
|
||
vkGetImageMemoryRequirements(vk.device, image, &mem_reqs2.memoryRequirements);
|
||
|
||
pooltype = vk_find_memory_try(mem_reqs2.memoryRequirements.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
|
||
if (pooltype == ~0)
|
||
pooltype = vk_find_memory_require(mem_reqs2.memoryRequirements.memoryTypeBits, 0);
|
||
|
||
if (!dedicated && VK_AllocatePoolMemory(pooltype, mem_reqs2.memoryRequirements.size, mem_reqs2.memoryRequirements.alignment, mem))
|
||
return true; //got a shared allocation.
|
||
else
|
||
{ //make it dedicated one way or another.
|
||
VkMemoryAllocateInfo memAllocInfo = {VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO};
|
||
VkMemoryDedicatedAllocateInfoKHR khr_mdai = {VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO_KHR};
|
||
VkResult err;
|
||
|
||
//shouldn't really happen, but just in case...
|
||
mem_reqs2.memoryRequirements.size = max(1,mem_reqs2.memoryRequirements.size);
|
||
|
||
memAllocInfo.allocationSize = mem_reqs2.memoryRequirements.size;
|
||
memAllocInfo.memoryTypeIndex = pooltype;
|
||
if (vk.khr_dedicated_allocation)
|
||
{
|
||
khr_mdai.image = image;
|
||
khr_mdai.pNext = memAllocInfo.pNext;
|
||
memAllocInfo.pNext = &khr_mdai;
|
||
}
|
||
|
||
mem->pool = NULL;
|
||
mem->offset = 0;
|
||
mem->size = mem_reqs2.memoryRequirements.size;
|
||
mem->memory = VK_NULL_HANDLE;
|
||
|
||
err = vkAllocateMemory(vk.device, &memAllocInfo, vkallocationcb, &mem->memory);
|
||
if (err != VK_SUCCESS)
|
||
return false;
|
||
return true;
|
||
}
|
||
}
|
||
qboolean VK_AllocateBindImageMemory(vk_image_t *image, qboolean dedicated)
|
||
{
|
||
if (VK_AllocateImageMemory(image->image, dedicated, &image->mem))
|
||
{
|
||
VkAssert(vkBindImageMemory(vk.device, image->image, image->mem.memory, image->mem.offset));
|
||
return true;
|
||
}
|
||
return false; //out of memory?
|
||
}
|
||
|
||
|
||
vk_image_t VK_CreateTexture2DArray(uint32_t width, uint32_t height, uint32_t layers, uint32_t mips, uploadfmt_t encoding, unsigned int type, qboolean rendertarget, const char *debugname)
|
||
{
|
||
vk_image_t ret;
|
||
VkImageViewCreateInfo viewInfo = {VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO};
|
||
VkImageCreateInfo ici = {VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO};
|
||
VkFormat format = VK_FORMAT_UNDEFINED;
|
||
#ifdef VK_EXT_astc_decode_mode
|
||
VkImageViewASTCDecodeModeEXT astcmode;
|
||
#endif
|
||
|
||
ret.width = width;
|
||
ret.height = height;
|
||
ret.layers = layers;
|
||
ret.mipcount = mips;
|
||
ret.encoding = encoding;
|
||
ret.type = type;
|
||
ret.layout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||
|
||
//vulkan expresses packed formats in terms of native endian (if big-endian, then everything makes sense), non-packed formats are expressed in byte order (consistent with big-endian).
|
||
//PTI formats are less well-defined...
|
||
if ((int)encoding < 0)
|
||
format = -(int)encoding;
|
||
else switch(encoding)
|
||
{
|
||
//16bit formats.
|
||
case PTI_RGB565: format = VK_FORMAT_R5G6B5_UNORM_PACK16; break;
|
||
case PTI_RGBA4444: format = VK_FORMAT_R4G4B4A4_UNORM_PACK16; break;
|
||
case PTI_ARGB4444: /*format = VK_FORMAT_A4R4G4B4_UNORM_PACK16;*/ break;
|
||
case PTI_RGBA5551: format = VK_FORMAT_R5G5B5A1_UNORM_PACK16; break;
|
||
case PTI_ARGB1555: format = VK_FORMAT_A1R5G5B5_UNORM_PACK16; break;
|
||
case PTI_R16: format = VK_FORMAT_R16_UNORM; break;
|
||
case PTI_RGBA16: format = VK_FORMAT_R16G16B16A16_UNORM; break;
|
||
//float formats
|
||
case PTI_R16F: format = VK_FORMAT_R16_SFLOAT; break;
|
||
case PTI_R32F: format = VK_FORMAT_R32_SFLOAT; break;
|
||
case PTI_RGBA16F: format = VK_FORMAT_R16G16B16A16_SFLOAT; break;
|
||
case PTI_RGBA32F: format = VK_FORMAT_R32G32B32A32_SFLOAT; break;
|
||
//weird formats
|
||
case PTI_P8:
|
||
case PTI_R8: format = VK_FORMAT_R8_UNORM; break;
|
||
case PTI_RG8: format = VK_FORMAT_R8G8_UNORM; break;
|
||
case PTI_R8_SNORM: format = VK_FORMAT_R8_SNORM; break;
|
||
case PTI_RG8_SNORM: format = VK_FORMAT_R8G8_SNORM; break;
|
||
case PTI_A2BGR10: format = VK_FORMAT_A2B10G10R10_UNORM_PACK32; break;
|
||
case PTI_E5BGR9: format = VK_FORMAT_E5B9G9R9_UFLOAT_PACK32; break;
|
||
case PTI_B10G11R11F: format = VK_FORMAT_B10G11R11_UFLOAT_PACK32; break;
|
||
//swizzled/legacy formats
|
||
case PTI_L8: format = VK_FORMAT_R8_UNORM; break;
|
||
case PTI_L8A8: format = VK_FORMAT_R8G8_UNORM; break;
|
||
case PTI_L8_SRGB: format = VK_FORMAT_R8_SRGB; break;
|
||
case PTI_L8A8_SRGB: /*unsupportable*/ break;
|
||
//compressed formats
|
||
case PTI_BC1_RGB: format = VK_FORMAT_BC1_RGB_UNORM_BLOCK; break;
|
||
case PTI_BC1_RGB_SRGB: format = VK_FORMAT_BC1_RGB_SRGB_BLOCK; break;
|
||
case PTI_BC1_RGBA: format = VK_FORMAT_BC1_RGBA_UNORM_BLOCK; break;
|
||
case PTI_BC1_RGBA_SRGB: format = VK_FORMAT_BC1_RGBA_SRGB_BLOCK; break;
|
||
case PTI_BC2_RGBA: format = VK_FORMAT_BC2_UNORM_BLOCK; break;
|
||
case PTI_BC2_RGBA_SRGB: format = VK_FORMAT_BC2_SRGB_BLOCK; break;
|
||
case PTI_BC3_RGBA: format = VK_FORMAT_BC3_UNORM_BLOCK; break;
|
||
case PTI_BC3_RGBA_SRGB: format = VK_FORMAT_BC3_SRGB_BLOCK; break;
|
||
case PTI_BC4_R: format = VK_FORMAT_BC4_UNORM_BLOCK; break;
|
||
case PTI_BC4_R_SNORM: format = VK_FORMAT_BC4_SNORM_BLOCK; break;
|
||
case PTI_BC5_RG: format = VK_FORMAT_BC5_UNORM_BLOCK; break;
|
||
case PTI_BC5_RG_SNORM: format = VK_FORMAT_BC5_SNORM_BLOCK; break;
|
||
case PTI_BC6_RGB_UFLOAT: format = VK_FORMAT_BC6H_UFLOAT_BLOCK; break;
|
||
case PTI_BC6_RGB_SFLOAT: format = VK_FORMAT_BC6H_SFLOAT_BLOCK; break;
|
||
case PTI_BC7_RGBA: format = VK_FORMAT_BC7_UNORM_BLOCK; break;
|
||
case PTI_BC7_RGBA_SRGB: format = VK_FORMAT_BC7_SRGB_BLOCK; break;
|
||
case PTI_ETC1_RGB8: format = VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK; break; //vulkan doesn't support etc1, but etc2 is a superset so its all okay.
|
||
case PTI_ETC2_RGB8: format = VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK; break;
|
||
case PTI_ETC2_RGB8_SRGB: format = VK_FORMAT_ETC2_R8G8B8_SRGB_BLOCK; break;
|
||
case PTI_ETC2_RGB8A1: format = VK_FORMAT_ETC2_R8G8B8A1_UNORM_BLOCK; break;
|
||
case PTI_ETC2_RGB8A1_SRGB: format = VK_FORMAT_ETC2_R8G8B8A1_SRGB_BLOCK; break;
|
||
case PTI_ETC2_RGB8A8: format = VK_FORMAT_ETC2_R8G8B8A8_UNORM_BLOCK; break;
|
||
case PTI_ETC2_RGB8A8_SRGB: format = VK_FORMAT_ETC2_R8G8B8A8_SRGB_BLOCK; break;
|
||
case PTI_EAC_R11: format = VK_FORMAT_EAC_R11_UNORM_BLOCK; break;
|
||
case PTI_EAC_R11_SNORM: format = VK_FORMAT_EAC_R11_SNORM_BLOCK; break;
|
||
case PTI_EAC_RG11: format = VK_FORMAT_EAC_R11G11_UNORM_BLOCK; break;
|
||
case PTI_EAC_RG11_SNORM: format = VK_FORMAT_EAC_R11G11_SNORM_BLOCK; break;
|
||
|
||
case PTI_ASTC_4X4_LDR: format = VK_FORMAT_ASTC_4x4_UNORM_BLOCK; break;
|
||
case PTI_ASTC_4X4_SRGB: format = VK_FORMAT_ASTC_4x4_SRGB_BLOCK; break;
|
||
case PTI_ASTC_5X4_LDR: format = VK_FORMAT_ASTC_5x4_UNORM_BLOCK; break;
|
||
case PTI_ASTC_5X4_SRGB: format = VK_FORMAT_ASTC_5x4_SRGB_BLOCK; break;
|
||
case PTI_ASTC_5X5_LDR: format = VK_FORMAT_ASTC_5x5_UNORM_BLOCK; break;
|
||
case PTI_ASTC_5X5_SRGB: format = VK_FORMAT_ASTC_5x5_SRGB_BLOCK; break;
|
||
case PTI_ASTC_6X5_LDR: format = VK_FORMAT_ASTC_6x5_UNORM_BLOCK; break;
|
||
case PTI_ASTC_6X5_SRGB: format = VK_FORMAT_ASTC_6x5_SRGB_BLOCK; break;
|
||
case PTI_ASTC_6X6_LDR: format = VK_FORMAT_ASTC_6x6_UNORM_BLOCK; break;
|
||
case PTI_ASTC_6X6_SRGB: format = VK_FORMAT_ASTC_6x6_SRGB_BLOCK; break;
|
||
case PTI_ASTC_8X5_LDR: format = VK_FORMAT_ASTC_8x5_UNORM_BLOCK; break;
|
||
case PTI_ASTC_8X5_SRGB: format = VK_FORMAT_ASTC_8x5_SRGB_BLOCK; break;
|
||
case PTI_ASTC_8X6_LDR: format = VK_FORMAT_ASTC_8x6_UNORM_BLOCK; break;
|
||
case PTI_ASTC_8X6_SRGB: format = VK_FORMAT_ASTC_8x6_SRGB_BLOCK; break;
|
||
case PTI_ASTC_8X8_LDR: format = VK_FORMAT_ASTC_8x8_UNORM_BLOCK; break;
|
||
case PTI_ASTC_8X8_SRGB: format = VK_FORMAT_ASTC_8x8_SRGB_BLOCK; break;
|
||
case PTI_ASTC_10X5_LDR: format = VK_FORMAT_ASTC_10x5_UNORM_BLOCK; break;
|
||
case PTI_ASTC_10X5_SRGB: format = VK_FORMAT_ASTC_10x5_SRGB_BLOCK; break;
|
||
case PTI_ASTC_10X6_LDR: format = VK_FORMAT_ASTC_10x6_UNORM_BLOCK; break;
|
||
case PTI_ASTC_10X6_SRGB: format = VK_FORMAT_ASTC_10x6_SRGB_BLOCK; break;
|
||
case PTI_ASTC_10X8_LDR: format = VK_FORMAT_ASTC_10x8_UNORM_BLOCK; break;
|
||
case PTI_ASTC_10X8_SRGB: format = VK_FORMAT_ASTC_10x8_SRGB_BLOCK; break;
|
||
case PTI_ASTC_10X10_LDR: format = VK_FORMAT_ASTC_10x10_UNORM_BLOCK; break;
|
||
case PTI_ASTC_10X10_SRGB: format = VK_FORMAT_ASTC_10x10_SRGB_BLOCK; break;
|
||
case PTI_ASTC_12X10_LDR: format = VK_FORMAT_ASTC_12x10_UNORM_BLOCK; break;
|
||
case PTI_ASTC_12X10_SRGB: format = VK_FORMAT_ASTC_12x10_SRGB_BLOCK; break;
|
||
case PTI_ASTC_12X12_LDR: format = VK_FORMAT_ASTC_12x12_UNORM_BLOCK; break;
|
||
case PTI_ASTC_12X12_SRGB: format = VK_FORMAT_ASTC_12x12_SRGB_BLOCK; break;
|
||
#ifdef VK_EXT_texture_compression_astc_hdr
|
||
case PTI_ASTC_4X4_HDR: format = VK_FORMAT_ASTC_4x4_SFLOAT_BLOCK_EXT; break;
|
||
case PTI_ASTC_5X4_HDR: format = VK_FORMAT_ASTC_5x4_SFLOAT_BLOCK_EXT; break;
|
||
case PTI_ASTC_5X5_HDR: format = VK_FORMAT_ASTC_5x5_SFLOAT_BLOCK_EXT; break;
|
||
case PTI_ASTC_6X5_HDR: format = VK_FORMAT_ASTC_6x5_SFLOAT_BLOCK_EXT; break;
|
||
case PTI_ASTC_6X6_HDR: format = VK_FORMAT_ASTC_6x6_SFLOAT_BLOCK_EXT; break;
|
||
case PTI_ASTC_8X5_HDR: format = VK_FORMAT_ASTC_8x5_SFLOAT_BLOCK_EXT; break;
|
||
case PTI_ASTC_8X6_HDR: format = VK_FORMAT_ASTC_8x6_SFLOAT_BLOCK_EXT; break;
|
||
case PTI_ASTC_8X8_HDR: format = VK_FORMAT_ASTC_8x8_SFLOAT_BLOCK_EXT; break;
|
||
case PTI_ASTC_10X5_HDR: format = VK_FORMAT_ASTC_10x5_SFLOAT_BLOCK_EXT; break;
|
||
case PTI_ASTC_10X6_HDR: format = VK_FORMAT_ASTC_10x6_SFLOAT_BLOCK_EXT; break;
|
||
case PTI_ASTC_10X8_HDR: format = VK_FORMAT_ASTC_10x8_SFLOAT_BLOCK_EXT; break;
|
||
case PTI_ASTC_10X10_HDR: format = VK_FORMAT_ASTC_10x10_SFLOAT_BLOCK_EXT; break;
|
||
case PTI_ASTC_12X10_HDR: format = VK_FORMAT_ASTC_12x10_SFLOAT_BLOCK_EXT; break;
|
||
case PTI_ASTC_12X12_HDR: format = VK_FORMAT_ASTC_12x12_SFLOAT_BLOCK_EXT; break;
|
||
#else //better than crashing.
|
||
case PTI_ASTC_4X4_HDR: format = VK_FORMAT_ASTC_4x4_UNORM_BLOCK; break;
|
||
case PTI_ASTC_5X4_HDR: format = VK_FORMAT_ASTC_5x4_UNORM_BLOCK; break;
|
||
case PTI_ASTC_5X5_HDR: format = VK_FORMAT_ASTC_5x5_UNORM_BLOCK; break;
|
||
case PTI_ASTC_6X5_HDR: format = VK_FORMAT_ASTC_6x5_UNORM_BLOCK; break;
|
||
case PTI_ASTC_6X6_HDR: format = VK_FORMAT_ASTC_6x6_UNORM_BLOCK; break;
|
||
case PTI_ASTC_8X5_HDR: format = VK_FORMAT_ASTC_8x5_UNORM_BLOCK; break;
|
||
case PTI_ASTC_8X6_HDR: format = VK_FORMAT_ASTC_8x6_UNORM_BLOCK; break;
|
||
case PTI_ASTC_8X8_HDR: format = VK_FORMAT_ASTC_8x8_UNORM_BLOCK; break;
|
||
case PTI_ASTC_10X5_HDR: format = VK_FORMAT_ASTC_10x5_UNORM_BLOCK; break;
|
||
case PTI_ASTC_10X6_HDR: format = VK_FORMAT_ASTC_10x6_UNORM_BLOCK; break;
|
||
case PTI_ASTC_10X8_HDR: format = VK_FORMAT_ASTC_10x8_UNORM_BLOCK; break;
|
||
case PTI_ASTC_10X10_HDR: format = VK_FORMAT_ASTC_10x10_UNORM_BLOCK; break;
|
||
case PTI_ASTC_12X10_HDR: format = VK_FORMAT_ASTC_12x10_UNORM_BLOCK; break;
|
||
case PTI_ASTC_12X12_HDR: format = VK_FORMAT_ASTC_12x12_UNORM_BLOCK; break;
|
||
#endif
|
||
|
||
#ifdef ASTC3D
|
||
case PTI_ASTC_3X3X3_HDR: //vulkan doesn't support these for some reason
|
||
case PTI_ASTC_4X3X3_HDR:
|
||
case PTI_ASTC_4X4X3_HDR:
|
||
case PTI_ASTC_4X4X4_HDR:
|
||
case PTI_ASTC_5X4X4_HDR:
|
||
case PTI_ASTC_5X5X4_HDR:
|
||
case PTI_ASTC_5X5X5_HDR:
|
||
case PTI_ASTC_6X5X5_HDR:
|
||
case PTI_ASTC_6X6X5_HDR:
|
||
case PTI_ASTC_6X6X6_HDR:
|
||
case PTI_ASTC_3X3X3_LDR:
|
||
case PTI_ASTC_4X3X3_LDR:
|
||
case PTI_ASTC_4X4X3_LDR:
|
||
case PTI_ASTC_4X4X4_LDR:
|
||
case PTI_ASTC_5X4X4_LDR:
|
||
case PTI_ASTC_5X5X4_LDR:
|
||
case PTI_ASTC_5X5X5_LDR:
|
||
case PTI_ASTC_6X5X5_LDR:
|
||
case PTI_ASTC_6X6X5_LDR:
|
||
case PTI_ASTC_6X6X6_LDR:
|
||
case PTI_ASTC_3X3X3_SRGB:
|
||
case PTI_ASTC_4X3X3_SRGB:
|
||
case PTI_ASTC_4X4X3_SRGB:
|
||
case PTI_ASTC_4X4X4_SRGB:
|
||
case PTI_ASTC_5X4X4_SRGB:
|
||
case PTI_ASTC_5X5X4_SRGB:
|
||
case PTI_ASTC_5X5X5_SRGB:
|
||
case PTI_ASTC_6X5X5_SRGB:
|
||
case PTI_ASTC_6X6X5_SRGB:
|
||
case PTI_ASTC_6X6X6_SRGB: break;
|
||
#endif
|
||
|
||
//depth formats
|
||
case PTI_DEPTH16: format = VK_FORMAT_D16_UNORM; break;
|
||
case PTI_DEPTH24: format = VK_FORMAT_X8_D24_UNORM_PACK32; break;
|
||
case PTI_DEPTH32: format = VK_FORMAT_D32_SFLOAT; break;
|
||
case PTI_DEPTH24_8: format = VK_FORMAT_D24_UNORM_S8_UINT; break;
|
||
//srgb formats
|
||
case PTI_BGRA8_SRGB:
|
||
case PTI_BGRX8_SRGB: format = VK_FORMAT_B8G8R8A8_SRGB; break;
|
||
case PTI_RGBA8_SRGB:
|
||
case PTI_RGBX8_SRGB: format = VK_FORMAT_R8G8B8A8_SRGB; break;
|
||
//standard formats
|
||
case PTI_BGRA8:
|
||
case PTI_BGRX8: format = VK_FORMAT_B8G8R8A8_UNORM; break;
|
||
case PTI_RGBA8:
|
||
case PTI_RGBX8: format = VK_FORMAT_R8G8B8A8_UNORM; break;
|
||
//misaligned formats
|
||
case PTI_RGB8: format = VK_FORMAT_R8G8B8_UNORM; break;
|
||
case PTI_BGR8: format = VK_FORMAT_B8G8R8_UNORM; break;
|
||
case PTI_RGB32F: format = VK_FORMAT_R32G32B32_SFLOAT; break;
|
||
|
||
case PTI_RGB8_SRGB: format = VK_FORMAT_R8G8B8_SRGB; break;
|
||
case PTI_BGR8_SRGB: format = VK_FORMAT_B8G8R8_SRGB; break;
|
||
|
||
//unsupported 'formats'
|
||
case PTI_MAX:
|
||
#ifdef FTE_TARGET_WEB
|
||
case PTI_WHOLEFILE:
|
||
#endif
|
||
case PTI_EMULATED:
|
||
break;
|
||
}
|
||
if (format == VK_FORMAT_UNDEFINED) //no default case means warnings for unsupported formats above.
|
||
Sys_Error("VK_CreateTexture2DArray: Unsupported image encoding: %u(%s)\n", encoding, Image_FormatName(encoding));
|
||
|
||
ici.flags = (ret.type==PTI_CUBE)?VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT:0;
|
||
ici.imageType = VK_IMAGE_TYPE_2D;
|
||
ici.format = format;
|
||
ici.extent.width = width;
|
||
ici.extent.height = height;
|
||
ici.extent.depth = 1;
|
||
ici.mipLevels = mips;
|
||
ici.arrayLayers = layers;
|
||
ici.samples = VK_SAMPLE_COUNT_1_BIT;
|
||
ici.tiling = VK_IMAGE_TILING_OPTIMAL;
|
||
ici.usage = VK_IMAGE_USAGE_SAMPLED_BIT|(rendertarget?0:VK_IMAGE_USAGE_TRANSFER_DST_BIT);
|
||
ici.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
||
ici.queueFamilyIndexCount = 0;
|
||
ici.pQueueFamilyIndices = NULL;
|
||
ici.initialLayout = ret.layout;
|
||
|
||
VkAssert(vkCreateImage(vk.device, &ici, vkallocationcb, &ret.image));
|
||
DebugSetName(VK_OBJECT_TYPE_IMAGE, (uint64_t)ret.image, debugname);
|
||
|
||
ret.view = VK_NULL_HANDLE;
|
||
ret.sampler = VK_NULL_HANDLE;
|
||
|
||
if (!VK_AllocateBindImageMemory(&ret, false))
|
||
return ret; //oom?
|
||
|
||
|
||
viewInfo.flags = 0;
|
||
viewInfo.image = ret.image;
|
||
switch(ret.type)
|
||
{
|
||
default:
|
||
return ret;
|
||
case PTI_CUBE:
|
||
viewInfo.viewType = VK_IMAGE_VIEW_TYPE_CUBE;
|
||
break;
|
||
case PTI_2D:
|
||
viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
|
||
break;
|
||
case PTI_2D_ARRAY:
|
||
viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D_ARRAY;
|
||
break;
|
||
}
|
||
viewInfo.format = format;
|
||
switch(encoding)
|
||
{
|
||
//formats that explicitly drop the alpha
|
||
case PTI_BC1_RGB:
|
||
case PTI_BC1_RGB_SRGB:
|
||
case PTI_RGBX8:
|
||
case PTI_RGBX8_SRGB:
|
||
case PTI_BGRX8:
|
||
case PTI_BGRX8_SRGB:
|
||
viewInfo.components.r = VK_COMPONENT_SWIZZLE_R;
|
||
viewInfo.components.g = VK_COMPONENT_SWIZZLE_G;
|
||
viewInfo.components.b = VK_COMPONENT_SWIZZLE_B;
|
||
viewInfo.components.a = VK_COMPONENT_SWIZZLE_ONE;
|
||
break;
|
||
case PTI_L8: //must be an R8 texture
|
||
case PTI_L8_SRGB: //must be an R8 texture
|
||
viewInfo.components.r = VK_COMPONENT_SWIZZLE_R;
|
||
viewInfo.components.g = VK_COMPONENT_SWIZZLE_R;
|
||
viewInfo.components.b = VK_COMPONENT_SWIZZLE_R;
|
||
viewInfo.components.a = VK_COMPONENT_SWIZZLE_ONE;
|
||
break;
|
||
case PTI_L8A8: //must be an RG8 texture
|
||
viewInfo.components.r = VK_COMPONENT_SWIZZLE_R;
|
||
viewInfo.components.g = VK_COMPONENT_SWIZZLE_R;
|
||
viewInfo.components.b = VK_COMPONENT_SWIZZLE_R;
|
||
viewInfo.components.a = VK_COMPONENT_SWIZZLE_G;
|
||
break;
|
||
default:
|
||
viewInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY;
|
||
viewInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY;
|
||
viewInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY;
|
||
viewInfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY;
|
||
break;
|
||
|
||
#ifdef VK_EXT_astc_decode_mode
|
||
case PTI_ASTC_4X4_LDR: //set these to use rgba8 decoding, because we know they're not hdr and the format is basically 8bit anyway.
|
||
case PTI_ASTC_5X4_LDR: //we do NOT do this for the hdr, as that would cause data loss.
|
||
case PTI_ASTC_5X5_LDR: //we do NOT do this for sRGB because its pointless.
|
||
case PTI_ASTC_6X5_LDR:
|
||
case PTI_ASTC_6X6_LDR:
|
||
case PTI_ASTC_8X5_LDR:
|
||
case PTI_ASTC_8X6_LDR:
|
||
case PTI_ASTC_8X8_LDR:
|
||
case PTI_ASTC_10X5_LDR:
|
||
case PTI_ASTC_10X6_LDR:
|
||
case PTI_ASTC_10X8_LDR:
|
||
case PTI_ASTC_10X10_LDR:
|
||
case PTI_ASTC_12X10_LDR:
|
||
case PTI_ASTC_12X12_LDR:
|
||
viewInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY;
|
||
viewInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY;
|
||
viewInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY;
|
||
viewInfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY;
|
||
if (vk.ext_astc_decode_mode)
|
||
{
|
||
astcmode.pNext = viewInfo.pNext;
|
||
astcmode.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_ASTC_DECODE_MODE_EXT;
|
||
astcmode.decodeMode = VK_FORMAT_R8G8B8A8_UNORM;
|
||
viewInfo.pNext = &astcmode;
|
||
}
|
||
break;
|
||
#endif
|
||
}
|
||
viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||
viewInfo.subresourceRange.baseMipLevel = 0;
|
||
viewInfo.subresourceRange.levelCount = mips;
|
||
viewInfo.subresourceRange.baseArrayLayer = 0;
|
||
viewInfo.subresourceRange.layerCount = layers;
|
||
VkAssert(vkCreateImageView(vk.device, &viewInfo, NULL, &ret.view));
|
||
|
||
return ret;
|
||
}
|
||
void set_image_layout(VkCommandBuffer cmd, VkImage image, VkImageAspectFlags aspectMask,
|
||
VkImageLayout old_image_layout, VkAccessFlags srcaccess, VkPipelineStageFlagBits srcstagemask,
|
||
VkImageLayout new_image_layout, VkAccessFlags dstaccess, VkPipelineStageFlagBits dststagemask)
|
||
{
|
||
//images have weird layout representations.
|
||
//we need to use a side-effect of memory barriers in order to convert from one layout to another, so that we can actually use the image.
|
||
VkImageMemoryBarrier imgbarrier = {VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER};
|
||
imgbarrier.pNext = NULL;
|
||
imgbarrier.srcAccessMask = srcaccess;
|
||
imgbarrier.dstAccessMask = dstaccess;
|
||
imgbarrier.oldLayout = old_image_layout;
|
||
imgbarrier.newLayout = new_image_layout;
|
||
imgbarrier.image = image;
|
||
imgbarrier.subresourceRange.aspectMask = aspectMask;
|
||
imgbarrier.subresourceRange.baseMipLevel = 0;
|
||
imgbarrier.subresourceRange.levelCount = 1;
|
||
imgbarrier.subresourceRange.baseArrayLayer = 0;
|
||
imgbarrier.subresourceRange.layerCount = 1;
|
||
imgbarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||
imgbarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||
/*
|
||
if (new_image_layout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) // Make sure anything that was copying from this image has completed
|
||
imgbarrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
|
||
else if (new_image_layout == VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL) // Make sure anything that was copying from this image has completed
|
||
imgbarrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
|
||
else if (new_image_layout == VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL)
|
||
imgbarrier.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
|
||
else if (new_image_layout == VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL)
|
||
imgbarrier.dstAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
|
||
else if (new_image_layout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) // Make sure any Copy or CPU writes to image are flushed
|
||
imgbarrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_INPUT_ATTACHMENT_READ_BIT;
|
||
|
||
if (old_image_layout == VK_IMAGE_LAYOUT_PREINITIALIZED)
|
||
imgbarrier.srcAccessMask = VK_ACCESS_HOST_WRITE_BIT;
|
||
else if (old_image_layout == VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL)
|
||
imgbarrier.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
|
||
else if (old_image_layout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL)
|
||
imgbarrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
|
||
*/
|
||
vkCmdPipelineBarrier(cmd, srcstagemask, dststagemask, 0, 0, NULL, 0, NULL, 1, &imgbarrier);
|
||
}
|
||
|
||
void VK_FencedCheck(void)
|
||
{
|
||
while(vk.fencework)
|
||
{
|
||
Sys_LockConditional(vk.submitcondition);
|
||
if (VK_SUCCESS == vkGetFenceStatus(vk.device, vk.fencework->fence))
|
||
{
|
||
struct vk_fencework *w;
|
||
w = vk.fencework;
|
||
vk.fencework = w->next;
|
||
if (!vk.fencework)
|
||
vk.fencework_last = NULL;
|
||
Sys_UnlockConditional(vk.submitcondition);
|
||
|
||
if (w->Passed)
|
||
w->Passed(w);
|
||
if (w->cbuf)
|
||
vkFreeCommandBuffers(vk.device, vk.cmdpool, 1, &w->cbuf);
|
||
if (w->fence)
|
||
vkDestroyFence(vk.device, w->fence, vkallocationcb);
|
||
Z_Free(w);
|
||
continue;
|
||
}
|
||
Sys_UnlockConditional(vk.submitcondition);
|
||
break;
|
||
}
|
||
}
|
||
//allocate and begin a commandbuffer so we can do the copies
|
||
void *VK_FencedBegin(void (*passed)(void *work), size_t worksize)
|
||
{
|
||
struct vk_fencework *w = BZ_Malloc(worksize?worksize:sizeof(*w));
|
||
|
||
VkCommandBufferAllocateInfo cbai = {VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO};
|
||
VkCommandBufferInheritanceInfo cmdinh = {VK_STRUCTURE_TYPE_COMMAND_BUFFER_INHERITANCE_INFO};
|
||
VkCommandBufferBeginInfo cmdinf = {VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO};
|
||
cbai.commandPool = vk.cmdpool;
|
||
cbai.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
|
||
cbai.commandBufferCount = 1;
|
||
VkAssert(vkAllocateCommandBuffers(vk.device, &cbai, &w->cbuf));
|
||
DebugSetName(VK_OBJECT_TYPE_COMMAND_BUFFER, (uint64_t)w->cbuf, "VK_FencedBegin");
|
||
cmdinf.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
|
||
cmdinf.pInheritanceInfo = &cmdinh;
|
||
vkBeginCommandBuffer(w->cbuf, &cmdinf);
|
||
|
||
w->Passed = passed;
|
||
w->next = NULL;
|
||
|
||
return w;
|
||
}
|
||
//end+submit a commandbuffer, and set up a fence so we know when its complete. this is not within the context of any frame, so make sure any textures are safe to rewrite early...
|
||
//completion can be signalled before the current frame finishes, so watch out for that too.
|
||
void VK_FencedSubmit(void *work)
|
||
{
|
||
struct vk_fencework *w = work;
|
||
VkFenceCreateInfo fenceinfo = {VK_STRUCTURE_TYPE_FENCE_CREATE_INFO};
|
||
|
||
if (w->cbuf)
|
||
vkEndCommandBuffer(w->cbuf);
|
||
|
||
//check if we can release anything yet.
|
||
VK_FencedCheck();
|
||
|
||
//FIXME: this seems to be an excessively expensive function.
|
||
vkCreateFence(vk.device, &fenceinfo, vkallocationcb, &w->fence);
|
||
|
||
VK_Submit_Work(w->cbuf, VK_NULL_HANDLE, 0, VK_NULL_HANDLE, w->fence, NULL, w);
|
||
}
|
||
|
||
void VK_FencedSync(void *work)
|
||
{
|
||
struct vk_fencework *w = work;
|
||
VK_FencedSubmit(w);
|
||
|
||
#ifdef MULTITHREAD
|
||
//okay, this is crazy, but it ensures that the work was submitted BEFORE the WaitForFence call.
|
||
//we should probably come up with a better sync method.
|
||
if (vk.submitthread)
|
||
{
|
||
qboolean nnsc = vk.neednewswapchain;
|
||
vk.neednewswapchain = true;
|
||
Sys_LockConditional(vk.submitcondition); //annoying, but required for it to be reliable with respect to other things.
|
||
Sys_ConditionSignal(vk.submitcondition);
|
||
Sys_UnlockConditional(vk.submitcondition);
|
||
Sys_WaitOnThread(vk.submitthread);
|
||
vk.submitthread = NULL;
|
||
|
||
while (vk.work)
|
||
{
|
||
Sys_LockConditional(vk.submitcondition);
|
||
VK_Submit_DoWork();
|
||
Sys_UnlockConditional(vk.submitcondition);
|
||
}
|
||
|
||
//we know all work is synced now...
|
||
|
||
vk.neednewswapchain = nnsc;
|
||
vk.submitthread = Sys_CreateThread("vksubmission", VK_Submit_Thread, NULL, THREADP_HIGHEST, 0);
|
||
}
|
||
#endif
|
||
|
||
//fixme: waiting for the fence while it may still be getting created by the worker is unsafe.
|
||
vkWaitForFences(vk.device, 1, &w->fence, VK_FALSE, UINT64_MAX);
|
||
}
|
||
|
||
//called to schedule the release of a resource that may be referenced by an active command buffer.
|
||
//the command buffer in question may even have not yet been submitted yet.
|
||
void *VK_AtFrameEnd(void (*frameended)(void *work), void *workdata, size_t worksize)
|
||
{
|
||
struct vk_frameend *w = Z_Malloc(sizeof(*w) + worksize);
|
||
|
||
w->FrameEnded = frameended;
|
||
w->next = vk.frameendjobs;
|
||
vk.frameendjobs = w;
|
||
|
||
if (workdata)
|
||
memcpy(w+1, workdata, worksize);
|
||
|
||
return w+1;
|
||
}
|
||
|
||
struct texturefence
|
||
{
|
||
struct vk_fencework w;
|
||
|
||
int mips;
|
||
VkBuffer stagingbuffer;
|
||
VkDeviceMemory stagingmemory;
|
||
};
|
||
static void VK_TextureLoaded(void *ctx)
|
||
{
|
||
struct texturefence *w = ctx;
|
||
vkDestroyBuffer(vk.device, w->stagingbuffer, vkallocationcb);
|
||
vkFreeMemory(vk.device, w->stagingmemory, vkallocationcb);
|
||
}
|
||
qboolean VK_LoadTextureMips (texid_t tex, const struct pendingtextureinfo *mips)
|
||
{
|
||
VkBufferCreateInfo bci = {VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO};
|
||
VkMemoryRequirements mem_reqs;
|
||
VkMemoryAllocateInfo memAllocInfo = {VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO};
|
||
void *mapdata;
|
||
|
||
struct texturefence *fence;
|
||
VkCommandBuffer vkloadcmd;
|
||
vk_image_t target;
|
||
uint32_t i;
|
||
uint32_t blockwidth, blockheight, blockdepth;
|
||
uint32_t blockbytes;
|
||
uint32_t layers;
|
||
uint32_t mipcount = mips->mipcount;
|
||
switch(mips->type)
|
||
{
|
||
case PTI_2D:
|
||
if (!mipcount || mips->mip[0].width == 0 || mips->mip[0].height == 0 || mips->mip[0].depth != 1)
|
||
return false;
|
||
break;
|
||
case PTI_2D_ARRAY:
|
||
if (!mipcount || mips->mip[0].width == 0 || mips->mip[0].height == 0 || mips->mip[0].depth == 0)
|
||
return false;
|
||
break;
|
||
case PTI_CUBE:
|
||
if (!mipcount || mips->mip[0].width == 0 || mips->mip[0].height == 0 || mips->mip[0].depth != 6)
|
||
return false;
|
||
break;
|
||
default:
|
||
return false;
|
||
}
|
||
|
||
layers = mips->mip[0].depth;
|
||
|
||
if (layers == 1 && mipcount > 1)
|
||
{ //npot mipmapped textures are awkward.
|
||
//vulkan floors.
|
||
for (i = 1; i < mipcount; i++)
|
||
{
|
||
if (mips->mip[i].width != max(1,(mips->mip[i-1].width>>1)) ||
|
||
mips->mip[i].height != max(1,(mips->mip[i-1].height>>1)))
|
||
{ //okay, this mip looks like it was sized wrongly.
|
||
mipcount = i;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
Image_BlockSizeForEncoding(mips->encoding, &blockbytes, &blockwidth, &blockheight, &blockdepth);
|
||
|
||
fence = VK_FencedBegin(VK_TextureLoaded, sizeof(*fence));
|
||
fence->mips = mipcount;
|
||
vkloadcmd = fence->w.cbuf;
|
||
|
||
//create our target image
|
||
|
||
if (tex->vkimage)
|
||
{
|
||
if (tex->vkimage->width != mips->mip[0].width ||
|
||
tex->vkimage->height != mips->mip[0].height ||
|
||
tex->vkimage->layers != layers ||
|
||
tex->vkimage->mipcount != mipcount ||
|
||
tex->vkimage->encoding != mips->encoding ||
|
||
tex->vkimage->type != mips->type)
|
||
{
|
||
VK_AtFrameEnd(VK_DestroyVkTexture_Delayed, tex->vkimage, sizeof(*tex->vkimage));
|
||
// vkDeviceWaitIdle(vk.device); //erk, we can't cope with a commandbuffer poking the texture while things happen
|
||
// VK_FencedCheck();
|
||
// VK_DestroyVkTexture(tex->vkimage);
|
||
Z_Free(tex->vkimage);
|
||
tex->vkimage = NULL;
|
||
}
|
||
}
|
||
|
||
if (tex->vkimage)
|
||
{
|
||
target = *tex->vkimage; //can reuse it
|
||
Z_Free(tex->vkimage);
|
||
//we're meant to be replacing the entire thing, so we can just transition from undefined here
|
||
// set_image_layout(vkloadcmd, target.image, VK_IMAGE_ASPECT_COLOR_BIT, VK_IMAGE_LAYOUT_UNDEFINED, VK_ACCESS_SHADER_READ_BIT, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_ACCESS_TRANSFER_WRITE_BIT);
|
||
|
||
{
|
||
//images have weird layout representations.
|
||
//we need to use a side-effect of memory barriers in order to convert from one layout to another, so that we can actually use the image.
|
||
VkImageMemoryBarrier imgbarrier = {VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER};
|
||
imgbarrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||
imgbarrier.newLayout = target.layout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
|
||
imgbarrier.image = target.image;
|
||
imgbarrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||
imgbarrier.subresourceRange.baseMipLevel = 0;
|
||
imgbarrier.subresourceRange.levelCount = mipcount;
|
||
imgbarrier.subresourceRange.baseArrayLayer = 0;
|
||
imgbarrier.subresourceRange.layerCount = layers;
|
||
imgbarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||
imgbarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||
|
||
imgbarrier.srcAccessMask = VK_ACCESS_SHADER_READ_BIT;
|
||
imgbarrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
|
||
vkCmdPipelineBarrier(vkloadcmd, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, NULL, 0, NULL, 1, &imgbarrier);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
target = VK_CreateTexture2DArray(mips->mip[0].width, mips->mip[0].height, layers, mipcount, mips->encoding, mips->type, !!(tex->flags&IF_RENDERTARGET), tex->ident);
|
||
|
||
if (target.mem.memory == VK_NULL_HANDLE)
|
||
{
|
||
VK_DestroyVkTexture(&target);
|
||
return false; //the alloc failed? can't copy to that which does not exist.
|
||
}
|
||
|
||
{
|
||
//images have weird layout representations.
|
||
//we need to use a side-effect of memory barriers in order to convert from one layout to another, so that we can actually use the image.
|
||
VkImageMemoryBarrier imgbarrier = {VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER};
|
||
imgbarrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||
imgbarrier.newLayout = target.layout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
|
||
imgbarrier.image = target.image;
|
||
imgbarrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||
imgbarrier.subresourceRange.baseMipLevel = 0;
|
||
imgbarrier.subresourceRange.levelCount = mipcount;
|
||
imgbarrier.subresourceRange.baseArrayLayer = 0;
|
||
imgbarrier.subresourceRange.layerCount = layers;
|
||
imgbarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||
imgbarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||
|
||
imgbarrier.srcAccessMask = 0;
|
||
imgbarrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
|
||
vkCmdPipelineBarrier(vkloadcmd, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, NULL, 0, NULL, 1, &imgbarrier);
|
||
}
|
||
}
|
||
|
||
//figure out how big our staging buffer needs to be
|
||
bci.size = 0;
|
||
for (i = 0; i < mipcount; i++)
|
||
{
|
||
uint32_t blockswidth = (mips->mip[i].width+blockwidth-1) / blockwidth;
|
||
uint32_t blocksheight = (mips->mip[i].height+blockheight-1) / blockheight;
|
||
uint32_t blocksdepth = (mips->mip[i].depth+blockdepth-1) / blockdepth;
|
||
bci.size += blockswidth*blocksheight*blocksdepth*blockbytes;
|
||
}
|
||
bci.flags = 0;
|
||
bci.usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
|
||
bci.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
||
bci.queueFamilyIndexCount = 0;
|
||
bci.pQueueFamilyIndices = NULL;
|
||
|
||
//FIXME: nvidia's vkCreateBuffer ends up calling NtYieldExecution.
|
||
//which is basically a waste of time, and its hurting framerates.
|
||
|
||
//create+map the staging buffer
|
||
VkAssert(vkCreateBuffer(vk.device, &bci, vkallocationcb, &fence->stagingbuffer));
|
||
vkGetBufferMemoryRequirements(vk.device, fence->stagingbuffer, &mem_reqs);
|
||
memAllocInfo.allocationSize = mem_reqs.size;
|
||
memAllocInfo.memoryTypeIndex = vk_find_memory_require(mem_reqs.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT);
|
||
if (VK_SUCCESS != vkAllocateMemory(vk.device, &memAllocInfo, vkallocationcb, &fence->stagingmemory))
|
||
{
|
||
VK_FencedSubmit(fence);
|
||
return false; //some sort of oom error?
|
||
}
|
||
VkAssert(vkBindBufferMemory(vk.device, fence->stagingbuffer, fence->stagingmemory, 0));
|
||
VkAssert(vkMapMemory(vk.device, fence->stagingmemory, 0, bci.size, 0, &mapdata));
|
||
if (!mapdata)
|
||
Sys_Error("Unable to map staging image\n");
|
||
|
||
bci.size = 0;
|
||
for (i = 0; i < mipcount; i++)
|
||
{
|
||
size_t mipofs = 0;
|
||
VkBufferImageCopy region;
|
||
//figure out the number of 'blocks' in the image.
|
||
//for non-compressed formats this is just the width directly.
|
||
//for compressed formats (ie: s3tc/dxt) we need to round up to deal with npot.
|
||
uint32_t blockswidth = (mips->mip[i].width+blockwidth-1) / blockwidth;
|
||
uint32_t blocksheight = (mips->mip[i].height+blockheight-1) / blockheight;
|
||
uint32_t blocksdepth = (mips->mip[i].depth+blockdepth-1) / blockdepth, z;
|
||
|
||
//build it in layers...
|
||
for (z = 0; z < blocksdepth; z++)
|
||
{
|
||
if (mips->mip[i].data)
|
||
memcpy((char*)mapdata + bci.size, (char*)mips->mip[i].data+mipofs, blockswidth*blockbytes*blocksheight*blockdepth);
|
||
else
|
||
memset((char*)mapdata + bci.size, 0, blockswidth*blockbytes*blocksheight*blockdepth);
|
||
|
||
//queue up a buffer->image copy for this mip
|
||
region.bufferOffset = bci.size;
|
||
region.bufferRowLength = blockswidth*blockwidth;
|
||
region.bufferImageHeight = blocksheight*blockheight;
|
||
region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||
region.imageSubresource.mipLevel = i;
|
||
region.imageSubresource.baseArrayLayer = z*blockdepth;
|
||
region.imageSubresource.layerCount = blockdepth;
|
||
region.imageOffset.x = 0;
|
||
region.imageOffset.y = 0;
|
||
region.imageOffset.z = 0;
|
||
region.imageExtent.width = mips->mip[i].width;
|
||
region.imageExtent.height = mips->mip[i].height;
|
||
region.imageExtent.depth = blockdepth;
|
||
|
||
vkCmdCopyBufferToImage(vkloadcmd, fence->stagingbuffer, target.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion);
|
||
bci.size += blockdepth*blockswidth*blocksheight*blockbytes;
|
||
mipofs += blockdepth*blockswidth*blocksheight*blockbytes;
|
||
}
|
||
}
|
||
vkUnmapMemory(vk.device, fence->stagingmemory);
|
||
|
||
//layouts are annoying. and weird.
|
||
{
|
||
//images have weird layout representations.
|
||
//we need to use a side-effect of memory barriers in order to convert from one layout to another, so that we can actually use the image.
|
||
VkImageMemoryBarrier imgbarrier = {VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER};
|
||
imgbarrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
|
||
imgbarrier.newLayout = target.layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
||
imgbarrier.image = target.image;
|
||
imgbarrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||
imgbarrier.subresourceRange.baseMipLevel = 0;
|
||
imgbarrier.subresourceRange.levelCount = mipcount;
|
||
imgbarrier.subresourceRange.baseArrayLayer = 0;
|
||
imgbarrier.subresourceRange.layerCount = layers;
|
||
imgbarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||
imgbarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||
|
||
imgbarrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
|
||
imgbarrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_INPUT_ATTACHMENT_READ_BIT;
|
||
vkCmdPipelineBarrier(vkloadcmd, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, 0, NULL, 0, NULL, 1, &imgbarrier);
|
||
}
|
||
|
||
VK_FencedSubmit(fence);
|
||
|
||
//FIXME: should probably reuse these samplers.
|
||
if (!target.sampler)
|
||
VK_CreateSampler(tex->flags, &target);
|
||
|
||
tex->vkdescriptor = VK_NULL_HANDLE;
|
||
|
||
tex->vkimage = Z_Malloc(sizeof(*tex->vkimage));
|
||
*tex->vkimage = target;
|
||
|
||
return true;
|
||
}
|
||
void VK_DestroyTexture (texid_t tex)
|
||
{
|
||
if (tex->vkimage)
|
||
{
|
||
VK_DestroyVkTexture(tex->vkimage);
|
||
Z_Free(tex->vkimage);
|
||
tex->vkimage = NULL;
|
||
}
|
||
tex->vkdescriptor = VK_NULL_HANDLE;
|
||
}
|
||
|
||
|
||
|
||
|
||
void VK_R_Init (void)
|
||
{
|
||
uint32_t white[6] = {~0u,~0u,~0u,~0u,~0u,~0u};
|
||
r_blackcubeimage = Image_CreateTexture("***blackcube***", NULL, IF_NEAREST|IF_TEXTYPE_CUBE|IF_NOPURGE);
|
||
Image_Upload(r_blackcubeimage, TF_RGBX32, NULL, NULL, 1, 1, 6, IF_NEAREST|IF_NOMIPMAP|IF_NOGAMMA|IF_TEXTYPE_CUBE);
|
||
|
||
r_whitecubeimage = Image_CreateTexture("***whitecube***", NULL, IF_NEAREST|IF_TEXTYPE_CUBE|IF_NOPURGE);
|
||
Image_Upload(r_whitecubeimage, TF_RGBX32, white, NULL, 1, 1, 6, IF_NEAREST|IF_NOMIPMAP|IF_NOGAMMA|IF_TEXTYPE_CUBE);
|
||
}
|
||
void VK_R_DeInit (void)
|
||
{
|
||
R_GAliasFlushSkinCache(true);
|
||
Surf_DeInit();
|
||
VK_Shutdown_PostProc();
|
||
VK_DestroySwapChain();
|
||
VKBE_Shutdown();
|
||
|
||
R2D_Shutdown();
|
||
Shader_Shutdown();
|
||
Image_Shutdown();
|
||
}
|
||
|
||
void VK_SetupViewPortProjection(qboolean flipy, vec3_t *eyeangorg, float *fovoverrides)
|
||
{
|
||
float fov_x, fov_y;
|
||
float fovv_x, fovv_y;
|
||
|
||
float fov_l, fov_r, fov_d, fov_u;
|
||
|
||
if (eyeangorg)
|
||
{
|
||
extern cvar_t in_vraim;
|
||
matrix3x4 basematrix;
|
||
matrix3x4 eyematrix;
|
||
matrix3x4 viewmatrix;
|
||
vec3_t newa;
|
||
|
||
Matrix3x4_RM_FromAngles(eyeangorg[0], eyeangorg[1], eyematrix[0]);
|
||
if (r_refdef.base_known)
|
||
{ //mod is specifying its own base ang+org.
|
||
Matrix3x4_RM_FromAngles(r_refdef.base_angles, r_refdef.base_origin, basematrix[0]);
|
||
}
|
||
else
|
||
{ //mod provides no info.
|
||
//client will fiddle with input_angles
|
||
newa[0] = newa[2] = 0; //ignore player pitch+roll. sorry. apply the eye's transform on top.
|
||
newa[1] = r_refdef.viewangles[1];
|
||
if (in_vraim.ival)
|
||
newa[1] -= SHORT2ANGLE(r_refdef.playerview->vrdev[VRDEV_HEAD].angles[YAW]);
|
||
Matrix3x4_RM_FromAngles(newa, r_refdef.vieworg, basematrix[0]);
|
||
}
|
||
Matrix3x4_Multiply(eyematrix[0], basematrix[0], viewmatrix[0]);
|
||
Matrix3x4_RM_ToVectors(viewmatrix[0], vpn, vright, vup, r_origin);
|
||
VectorNegate(vright, vright);
|
||
|
||
}
|
||
else
|
||
{
|
||
AngleVectors (r_refdef.viewangles, vpn, vright, vup);
|
||
VectorCopy (r_refdef.vieworg, r_origin);
|
||
}
|
||
|
||
// screenaspect = (float)r_refdef.vrect.width/r_refdef.vrect.height;
|
||
|
||
/*view matrix*/
|
||
if (flipy) //mimic gl and give bottom-up
|
||
{
|
||
vec3_t down;
|
||
VectorNegate(vup, down);
|
||
VectorCopy(down, vup);
|
||
Matrix4x4_CM_ModelViewMatrixFromAxis(r_refdef.m_view, vpn, vright, down, r_refdef.vieworg);
|
||
r_refdef.flipcull = SHADER_CULL_FRONT | SHADER_CULL_BACK;
|
||
}
|
||
else
|
||
{
|
||
Matrix4x4_CM_ModelViewMatrixFromAxis(r_refdef.m_view, vpn, vright, vup, r_refdef.vieworg);
|
||
r_refdef.flipcull = 0;
|
||
}
|
||
|
||
fov_x = r_refdef.fov_x;//+sin(cl.time)*5;
|
||
fov_y = r_refdef.fov_y;//-sin(cl.time+1)*5;
|
||
fovv_x = r_refdef.fovv_x;
|
||
fovv_y = r_refdef.fovv_y;
|
||
if ((r_refdef.flags & RDF_UNDERWATER) && !(r_refdef.flags & RDF_WATERWARP))
|
||
{
|
||
fov_x *= 1 + (((sin(cl.time * 4.7) + 1) * 0.015) * r_waterwarp.value);
|
||
fov_y *= 1 + (((sin(cl.time * 3.0) + 1) * 0.015) * r_waterwarp.value);
|
||
fovv_x *= 1 + (((sin(cl.time * 4.7) + 1) * 0.015) * r_waterwarp.value);
|
||
fovv_y *= 1 + (((sin(cl.time * 3.0) + 1) * 0.015) * r_waterwarp.value);
|
||
}
|
||
if (fovoverrides)
|
||
{
|
||
fov_l = fovoverrides[0];
|
||
fov_r = fovoverrides[1];
|
||
fov_d = fovoverrides[2];
|
||
fov_u = fovoverrides[3];
|
||
|
||
fov_x = fov_r-fov_l;
|
||
fov_y = fov_u-fov_d;
|
||
|
||
fovv_x = fov_x;
|
||
fovv_y = fov_y;
|
||
r_refdef.flipcull = ((fov_u < fov_d)^(fov_r < fov_l))?SHADER_CULL_FLIP:0;
|
||
}
|
||
else
|
||
{
|
||
fov_l = -fov_x / 2;
|
||
fov_r = fov_x / 2;
|
||
fov_d = -fov_y / 2;
|
||
fov_u = fov_y / 2;
|
||
}
|
||
|
||
Matrix4x4_CM_Projection_Offset(r_refdef.m_projection_std, fov_l, fov_r, fov_d, fov_u, r_refdef.mindist, r_refdef.maxdist, false);
|
||
Matrix4x4_CM_Projection_Offset(r_refdef.m_projection_view, -fovv_x/2, fovv_x/2, -fovv_y/2, fovv_y/2, r_refdef.mindist, r_refdef.maxdist, false);
|
||
|
||
r_refdef.m_projection_view[2+4*0] *= 0.333;
|
||
r_refdef.m_projection_view[2+4*1] *= 0.333;
|
||
r_refdef.m_projection_view[2+4*2] *= 0.333;
|
||
r_refdef.m_projection_view[2+4*3] *= 0.333;
|
||
}
|
||
|
||
void VK_Set2D(void)
|
||
{
|
||
vid.fbvwidth = vid.width;
|
||
vid.fbvheight = vid.height;
|
||
vid.fbpwidth = vid.pixelwidth;
|
||
vid.fbpheight = vid.pixelheight;
|
||
|
||
r_refdef.pxrect.x = 0;
|
||
r_refdef.pxrect.y = 0;
|
||
r_refdef.pxrect.width = vid.fbpwidth;
|
||
r_refdef.pxrect.height = vid.fbpheight;
|
||
r_refdef.pxrect.maxheight = vid.pixelheight;
|
||
|
||
/*
|
||
{
|
||
VkClearDepthStencilValue val;
|
||
VkImageSubresourceRange range;
|
||
val.depth = 1;
|
||
val.stencil = 0;
|
||
range.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT;
|
||
range.baseArrayLayer = 0;
|
||
range.baseMipLevel = 0;
|
||
range.layerCount = 1;
|
||
range.levelCount = 1;
|
||
vkCmdClearDepthStencilImage(vk.frame->cbuf, vk.depthbuf.image, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, &val, 1, &range);
|
||
}
|
||
*/
|
||
|
||
/*
|
||
vkCmdEndRenderPass(vk.frame->cbuf);
|
||
{
|
||
VkRenderPassBeginInfo rpiinfo = {VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO};
|
||
VkClearValue clearvalues[1];
|
||
clearvalues[0].depthStencil.depth = 1.0;
|
||
clearvalues[0].depthStencil.stencil = 0;
|
||
rpiinfo.renderPass = VK_GetRenderPass(RP_CLEARDEPTH);
|
||
rpiinfo.renderArea.offset.x = r_refdef.pxrect.x;
|
||
rpiinfo.renderArea.offset.y = r_refdef.pxrect.y;
|
||
rpiinfo.renderArea.extent.width = r_refdef.pxrect.width;
|
||
rpiinfo.renderArea.extent.height = r_refdef.pxrect.height;
|
||
rpiinfo.framebuffer = vk.frame->backbuf->framebuffer;
|
||
rpiinfo.clearValueCount = 1;
|
||
rpiinfo.pClearValues = clearvalues;
|
||
vkCmdBeginRenderPass(vk.frame->cbuf, &rpiinfo, VK_SUBPASS_CONTENTS_INLINE);
|
||
}
|
||
*/
|
||
{
|
||
VkViewport vp[1];
|
||
VkRect2D scissor[1];
|
||
vp[0].x = r_refdef.pxrect.x;
|
||
vp[0].y = r_refdef.pxrect.y;
|
||
vp[0].width = r_refdef.pxrect.width;
|
||
vp[0].height = r_refdef.pxrect.height;
|
||
vp[0].minDepth = 0.0;
|
||
vp[0].maxDepth = 1.0;
|
||
scissor[0].offset.x = r_refdef.pxrect.x;
|
||
scissor[0].offset.y = r_refdef.pxrect.y;
|
||
scissor[0].extent.width = r_refdef.pxrect.width;
|
||
scissor[0].extent.height = r_refdef.pxrect.height;
|
||
vkCmdSetViewport(vk.rendertarg->cbuf, 0, countof(vp), vp);
|
||
vkCmdSetScissor(vk.rendertarg->cbuf, 0, countof(scissor), scissor);
|
||
}
|
||
|
||
VKBE_Set2D(true);
|
||
|
||
if (0)
|
||
Matrix4x4_CM_Orthographic(r_refdef.m_projection_std, 0, vid.fbvwidth, 0, vid.fbvheight, -99999, 99999);
|
||
else
|
||
Matrix4x4_CM_Orthographic(r_refdef.m_projection_std, 0, vid.fbvwidth, vid.fbvheight, 0, -99999, 99999);
|
||
Matrix4x4_Identity(r_refdef.m_view);
|
||
|
||
BE_SelectEntity(&r_worldentity);
|
||
}
|
||
|
||
static void VK_Shutdown_PostProc(void)
|
||
{
|
||
unsigned int i;
|
||
if (vk.device)
|
||
{
|
||
for (i = 0; i < countof(postproc); i++)
|
||
VKBE_RT_Gen(&postproc[i], NULL, 0, 0, true, RT_IMAGEFLAGS);
|
||
VK_R_BloomShutdown();
|
||
}
|
||
|
||
vk.scenepp_waterwarp = NULL;
|
||
vk.scenepp_antialias = NULL;
|
||
}
|
||
static void VK_Init_PostProc(void)
|
||
{
|
||
texid_t scenepp_texture_warp, scenepp_texture_edge;
|
||
//this block liberated from the opengl code
|
||
{
|
||
#define PP_WARP_TEX_SIZE 64
|
||
#define PP_AMP_TEX_SIZE 64
|
||
#define PP_AMP_TEX_BORDER 4
|
||
int i, x, y;
|
||
unsigned char pp_warp_tex[PP_WARP_TEX_SIZE*PP_WARP_TEX_SIZE*4];
|
||
unsigned char pp_edge_tex[PP_AMP_TEX_SIZE*PP_AMP_TEX_SIZE*4];
|
||
|
||
// scenepp_postproc_cube = r_nulltex;
|
||
|
||
// TEXASSIGN(sceneblur_texture, Image_CreateTexture("***postprocess_blur***", NULL, 0));
|
||
|
||
TEXASSIGN(scenepp_texture_warp, Image_CreateTexture("***postprocess_warp***", NULL, IF_NOMIPMAP|IF_NOGAMMA|IF_LINEAR));
|
||
TEXASSIGN(scenepp_texture_edge, Image_CreateTexture("***postprocess_edge***", NULL, IF_NOMIPMAP|IF_NOGAMMA|IF_LINEAR));
|
||
|
||
// init warp texture - this specifies offset in
|
||
for (y=0; y<PP_WARP_TEX_SIZE; y++)
|
||
{
|
||
for (x=0; x<PP_WARP_TEX_SIZE; x++)
|
||
{
|
||
float fx, fy;
|
||
|
||
i = (x + y*PP_WARP_TEX_SIZE) * 4;
|
||
|
||
fx = sin(((double)y / PP_WARP_TEX_SIZE) * M_PI * 2);
|
||
fy = cos(((double)x / PP_WARP_TEX_SIZE) * M_PI * 2);
|
||
|
||
pp_warp_tex[i ] = (fx+1.0f)*127.0f;
|
||
pp_warp_tex[i+1] = (fy+1.0f)*127.0f;
|
||
pp_warp_tex[i+2] = 0;
|
||
pp_warp_tex[i+3] = 0xff;
|
||
}
|
||
}
|
||
|
||
Image_Upload(scenepp_texture_warp, TF_RGBX32, pp_warp_tex, NULL, PP_WARP_TEX_SIZE, PP_WARP_TEX_SIZE, 1, IF_LINEAR|IF_NOMIPMAP|IF_NOGAMMA);
|
||
|
||
// TODO: init edge texture - this is ampscale * 2, with ampscale calculated
|
||
// init warp texture - this specifies offset in
|
||
for (y=0; y<PP_AMP_TEX_SIZE; y++)
|
||
{
|
||
for (x=0; x<PP_AMP_TEX_SIZE; x++)
|
||
{
|
||
float fx = 1, fy = 1;
|
||
|
||
i = (x + y*PP_AMP_TEX_SIZE) * 4;
|
||
|
||
if (x < PP_AMP_TEX_BORDER)
|
||
{
|
||
fx = (float)x / PP_AMP_TEX_BORDER;
|
||
}
|
||
if (x > PP_AMP_TEX_SIZE - PP_AMP_TEX_BORDER)
|
||
{
|
||
fx = (PP_AMP_TEX_SIZE - (float)x) / PP_AMP_TEX_BORDER;
|
||
}
|
||
|
||
if (y < PP_AMP_TEX_BORDER)
|
||
{
|
||
fy = (float)y / PP_AMP_TEX_BORDER;
|
||
}
|
||
if (y > PP_AMP_TEX_SIZE - PP_AMP_TEX_BORDER)
|
||
{
|
||
fy = (PP_AMP_TEX_SIZE - (float)y) / PP_AMP_TEX_BORDER;
|
||
}
|
||
|
||
//avoid any sudden changes.
|
||
fx=sin(fx*M_PI*0.5);
|
||
fy=sin(fy*M_PI*0.5);
|
||
|
||
//lame
|
||
fx = fy = min(fx, fy);
|
||
|
||
pp_edge_tex[i ] = fx * 255;
|
||
pp_edge_tex[i+1] = fy * 255;
|
||
pp_edge_tex[i+2] = 0;
|
||
pp_edge_tex[i+3] = 0xff;
|
||
}
|
||
}
|
||
|
||
Image_Upload(scenepp_texture_edge, TF_RGBX32, pp_edge_tex, NULL, PP_AMP_TEX_SIZE, PP_AMP_TEX_SIZE, 1, IF_LINEAR|IF_NOMIPMAP|IF_NOGAMMA);
|
||
}
|
||
|
||
|
||
vk.scenepp_waterwarp = R_RegisterShader("waterwarp", SUF_NONE,
|
||
"{\n"
|
||
"program underwaterwarp\n"
|
||
"{\n"
|
||
"map $sourcecolour\n"
|
||
"}\n"
|
||
"{\n"
|
||
"map $upperoverlay\n"
|
||
"}\n"
|
||
"{\n"
|
||
"map $loweroverlay\n"
|
||
"}\n"
|
||
"}\n"
|
||
);
|
||
vk.scenepp_waterwarp->defaulttextures->upperoverlay = scenepp_texture_warp;
|
||
vk.scenepp_waterwarp->defaulttextures->loweroverlay = scenepp_texture_edge;
|
||
|
||
vk.scenepp_antialias = R_RegisterShader("fte_ppantialias", 0,
|
||
"{\n"
|
||
"program fxaa\n"
|
||
"{\n"
|
||
"map $sourcecolour\n"
|
||
"}\n"
|
||
"}\n"
|
||
);
|
||
}
|
||
|
||
|
||
|
||
static qboolean VK_R_RenderScene_Cubemap(struct vk_rendertarg *fb)
|
||
{
|
||
int cmapsize = 512;
|
||
int i;
|
||
static vec3_t ang[6] =
|
||
{ {0, -90, 0}, {0, 90, 0},
|
||
{90, 0, 0}, {-90, 0, 0},
|
||
{0, 0, 0}, {0, -180, 0} };
|
||
vec3_t saveang;
|
||
vec3_t saveorg;
|
||
|
||
vrect_t vrect;
|
||
pxrect_t prect;
|
||
extern cvar_t ffov;
|
||
|
||
shader_t *shader;
|
||
int facemask;
|
||
extern cvar_t r_projection;
|
||
int osm;
|
||
struct vk_rendertarg_cube *rtc = &vk_rt_cubemap;
|
||
|
||
if (!*ffov.string || !strcmp(ffov.string, "0"))
|
||
{
|
||
if (ffov.vec4[0] != scr_fov.value)
|
||
{
|
||
ffov.value = ffov.vec4[0] = scr_fov.value;
|
||
Shader_NeedReload(false); //gah!
|
||
}
|
||
}
|
||
|
||
facemask = 0;
|
||
switch(r_projection.ival)
|
||
{
|
||
default: //invalid.
|
||
return false;
|
||
case PROJ_STEREOGRAPHIC:
|
||
shader = R_RegisterShader("postproc_stereographic", SUF_NONE,
|
||
"{\n"
|
||
"program postproc_stereographic\n"
|
||
"{\n"
|
||
"map $sourcecube\n"
|
||
"}\n"
|
||
"}\n"
|
||
);
|
||
|
||
facemask |= 1<<4; /*front view*/
|
||
if (ffov.value > 70)
|
||
{
|
||
facemask |= (1<<0) | (1<<1); /*side/top*/
|
||
if (ffov.value > 85)
|
||
facemask |= (1<<2) | (1<<3); /*bottom views*/
|
||
if (ffov.value > 300)
|
||
facemask |= 1<<5; /*back view*/
|
||
}
|
||
break;
|
||
case PROJ_FISHEYE:
|
||
shader = R_RegisterShader("postproc_fisheye", SUF_NONE,
|
||
"{\n"
|
||
"program postproc_fisheye\n"
|
||
"{\n"
|
||
"map $sourcecube\n"
|
||
"}\n"
|
||
"}\n"
|
||
);
|
||
|
||
//fisheye view sees up to a full sphere
|
||
facemask |= 1<<4; /*front view*/
|
||
if (ffov.value > 77)
|
||
facemask |= (1<<0) | (1<<1) | (1<<2) | (1<<3); /*side/top/bottom views*/
|
||
if (ffov.value > 270)
|
||
facemask |= 1<<5; /*back view*/
|
||
break;
|
||
case PROJ_PANORAMA:
|
||
shader = R_RegisterShader("postproc_panorama", SUF_NONE,
|
||
"{\n"
|
||
"program postproc_panorama\n"
|
||
"{\n"
|
||
"map $sourcecube\n"
|
||
"}\n"
|
||
"}\n"
|
||
);
|
||
|
||
//panoramic view needs at most the four sides
|
||
facemask |= 1<<4; /*front view*/
|
||
if (ffov.value > 90)
|
||
{
|
||
facemask |= (1<<0) | (1<<1); /*side views*/
|
||
if (ffov.value > 270)
|
||
facemask |= 1<<5; /*back view*/
|
||
}
|
||
facemask = 0x3f;
|
||
break;
|
||
case PROJ_LAEA:
|
||
shader = R_RegisterShader("postproc_laea", SUF_NONE,
|
||
"{\n"
|
||
"program postproc_laea\n"
|
||
"{\n"
|
||
"map $sourcecube\n"
|
||
"}\n"
|
||
"}\n"
|
||
);
|
||
|
||
facemask |= 1<<4; /*front view*/
|
||
if (ffov.value > 90)
|
||
{
|
||
facemask |= (1<<0) | (1<<1) | (1<<2) | (1<<3); /*side/top/bottom views*/
|
||
if (ffov.value > 270)
|
||
facemask |= 1<<5; /*back view*/
|
||
}
|
||
break;
|
||
|
||
case PROJ_EQUIRECTANGULAR:
|
||
shader = R_RegisterShader("postproc_equirectangular", SUF_NONE,
|
||
"{\n"
|
||
"program postproc_equirectangular\n"
|
||
"{\n"
|
||
"map $sourcecube\n"
|
||
"}\n"
|
||
"}\n"
|
||
);
|
||
|
||
facemask = 0x3f;
|
||
#if 0
|
||
facemask |= 1<<4; /*front view*/
|
||
if (ffov.value > 90)
|
||
{
|
||
facemask |= (1<<0) | (1<<1) | (1<<2) | (1<<3); /*side/top/bottom views*/
|
||
if (ffov.value > 270)
|
||
facemask |= 1<<5; /*back view*/
|
||
}
|
||
#endif
|
||
break;
|
||
}
|
||
|
||
if (!shader || !shader->prog)
|
||
return false; //erk. shader failed.
|
||
|
||
//FIXME: we should be able to rotate the view
|
||
|
||
vrect = r_refdef.vrect;
|
||
prect = r_refdef.pxrect;
|
||
// prect.x = (vrect.x * vid.pixelwidth)/vid.width;
|
||
// prect.width = (vrect.width * vid.pixelwidth)/vid.width;
|
||
// prect.y = (vrect.y * vid.pixelheight)/vid.height;
|
||
// prect.height = (vrect.height * vid.pixelheight)/vid.height;
|
||
|
||
if (sh_config.texture_non_power_of_two_pic)
|
||
{
|
||
cmapsize = prect.width > prect.height?prect.width:prect.height;
|
||
if (cmapsize > 4096)//sh_config.texture_maxsize)
|
||
cmapsize = 4096;//sh_config.texture_maxsize;
|
||
}
|
||
|
||
|
||
r_refdef.flags |= RDF_FISHEYE;
|
||
vid.fbpwidth = vid.fbpheight = cmapsize;
|
||
|
||
//FIXME: gl_max_size
|
||
|
||
VectorCopy(r_refdef.vieworg, saveorg);
|
||
VectorCopy(r_refdef.viewangles, saveang);
|
||
saveang[2] = 0;
|
||
|
||
osm = r_refdef.stereomethod;
|
||
r_refdef.stereomethod = STEREO_OFF;
|
||
|
||
VKBE_RT_Gen_Cube(rtc, cmapsize, r_clear.ival?true:false);
|
||
|
||
vrect = r_refdef.vrect; //save off the old vrect
|
||
|
||
r_refdef.vrect.width = (cmapsize * vid.fbvwidth) / vid.fbpwidth;
|
||
r_refdef.vrect.height = (cmapsize * vid.fbvheight) / vid.fbpheight;
|
||
r_refdef.vrect.x = 0;
|
||
r_refdef.vrect.y = prect.y;
|
||
|
||
ang[0][0] = -saveang[0];
|
||
ang[0][1] = -90;
|
||
ang[0][2] = -saveang[0];
|
||
|
||
ang[1][0] = -saveang[0];
|
||
ang[1][1] = 90;
|
||
ang[1][2] = saveang[0];
|
||
ang[5][0] = -saveang[0]*2;
|
||
|
||
//in theory, we could use a geometry shader to duplicate the polygons to each face.
|
||
//that would of course require that every bit of glsl had such a geometry shader.
|
||
//it would at least reduce cpu load quite a bit.
|
||
for (i = 0; i < 6; i++)
|
||
{
|
||
if (!(facemask & (1<<i)))
|
||
continue;
|
||
|
||
VKBE_RT_Begin(&rtc->face[i]);
|
||
|
||
r_refdef.fov_x = 90;
|
||
r_refdef.fov_y = 90;
|
||
r_refdef.viewangles[0] = saveang[0]+ang[i][0];
|
||
r_refdef.viewangles[1] = saveang[1]+ang[i][1];
|
||
r_refdef.viewangles[2] = saveang[2]+ang[i][2];
|
||
|
||
|
||
VK_SetupViewPortProjection(true, NULL, NULL);
|
||
|
||
/*if (!vk.rendertarg->depthcleared)
|
||
{
|
||
VkClearAttachment clr;
|
||
VkClearRect rect;
|
||
clr.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT;
|
||
clr.clearValue.depthStencil.depth = 1;
|
||
clr.clearValue.depthStencil.stencil = 0;
|
||
clr.colorAttachment = 1;
|
||
rect.rect.offset.x = r_refdef.pxrect.x;
|
||
rect.rect.offset.y = r_refdef.pxrect.y;
|
||
rect.rect.extent.width = r_refdef.pxrect.width;
|
||
rect.rect.extent.height = r_refdef.pxrect.height;
|
||
rect.layerCount = 1;
|
||
rect.baseArrayLayer = 0;
|
||
vkCmdClearAttachments(vk.frame->cbuf, 1, &clr, 1, &rect);
|
||
vk.rendertarg->depthcleared = true;
|
||
}*/
|
||
|
||
VKBE_SelectEntity(&r_worldentity);
|
||
|
||
R_SetFrustum (r_refdef.m_projection_std, r_refdef.m_view);
|
||
RQ_BeginFrame();
|
||
if (!(r_refdef.flags & RDF_NOWORLDMODEL))
|
||
{
|
||
if (cl.worldmodel)
|
||
P_DrawParticles ();
|
||
}
|
||
Surf_DrawWorld();
|
||
RQ_RenderBatchClear();
|
||
|
||
vk.rendertarg->depthcleared = false;
|
||
|
||
if (R2D_Flush)
|
||
Con_Printf("no flush\n");
|
||
|
||
VKBE_RT_End(&rtc->face[i]);
|
||
}
|
||
|
||
r_refdef.vrect = vrect;
|
||
r_refdef.pxrect = prect;
|
||
VectorCopy(saveorg, r_refdef.vieworg);
|
||
r_refdef.stereomethod = osm;
|
||
|
||
VKBE_RT_Begin(fb);
|
||
|
||
r_refdef.flipcull = 0;
|
||
VK_Set2D();
|
||
|
||
shader->defaulttextures->reflectcube = &rtc->q_colour;
|
||
|
||
// draw it through the shader
|
||
if (r_projection.ival == PROJ_EQUIRECTANGULAR)
|
||
{
|
||
//note vr screenshots have requirements here
|
||
R2D_Image(vrect.x, vrect.y, vrect.width, vrect.height, 0, 1, 1, 0, shader);
|
||
}
|
||
else if (r_projection.ival == PROJ_PANORAMA)
|
||
{
|
||
float saspect = .5;
|
||
float taspect = vrect.height / vrect.width * ffov.value / 90;//(0.5 * vrect.width) / vrect.height;
|
||
R2D_Image(vrect.x, vrect.y, vrect.width, vrect.height, -saspect, taspect, saspect, -taspect, shader);
|
||
}
|
||
else if (vrect.width > vrect.height)
|
||
{
|
||
float aspect = (0.5 * vrect.height) / vrect.width;
|
||
R2D_Image(vrect.x, vrect.y, vrect.width, vrect.height, -0.5, aspect, 0.5, -aspect, shader);
|
||
}
|
||
else
|
||
{
|
||
float aspect = (0.5 * vrect.width) / vrect.height;
|
||
R2D_Image(vrect.x, vrect.y, vrect.width, vrect.height, -aspect, 0.5, aspect, -0.5, shader);
|
||
}
|
||
|
||
if (R2D_Flush)
|
||
R2D_Flush();
|
||
|
||
return true;
|
||
}
|
||
|
||
void VK_R_RenderEye(texid_t image, vec4_t fovoverride, vec3_t eyeangorg[2])
|
||
{
|
||
struct vk_rendertarg *rt;
|
||
|
||
VK_SetupViewPortProjection(false, eyeangorg, fovoverride);
|
||
|
||
rt = &postproc[postproc_buf++%countof(postproc)];
|
||
rt->rpassflags |= RP_VR;
|
||
VKBE_RT_Gen(rt, image?image->vkimage:NULL, 320, 200, false, RT_IMAGEFLAGS);
|
||
VKBE_RT_Begin(rt);
|
||
|
||
|
||
if (!vk.rendertarg->depthcleared)
|
||
{
|
||
VkClearAttachment clr;
|
||
VkClearRect rect;
|
||
clr.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT;
|
||
clr.clearValue.depthStencil.depth = 1;
|
||
clr.clearValue.depthStencil.stencil = 0;
|
||
clr.colorAttachment = 1;
|
||
rect.rect.offset.x = r_refdef.pxrect.x;
|
||
rect.rect.offset.y = r_refdef.pxrect.y;
|
||
rect.rect.extent.width = r_refdef.pxrect.width;
|
||
rect.rect.extent.height = r_refdef.pxrect.height;
|
||
rect.layerCount = 1;
|
||
rect.baseArrayLayer = 0;
|
||
vkCmdClearAttachments(vk.rendertarg->cbuf, 1, &clr, 1, &rect);
|
||
vk.rendertarg->depthcleared = true;
|
||
}
|
||
|
||
VKBE_SelectEntity(&r_worldentity);
|
||
|
||
R_SetFrustum (r_refdef.m_projection_std, r_refdef.m_view);
|
||
RQ_BeginFrame();
|
||
if (!(r_refdef.flags & RDF_NOWORLDMODEL))
|
||
{
|
||
if (cl.worldmodel)
|
||
P_DrawParticles ();
|
||
}
|
||
Surf_DrawWorld();
|
||
RQ_RenderBatchClear();
|
||
|
||
vk.rendertarg->depthcleared = false;
|
||
|
||
VKBE_RT_End(rt);
|
||
rt->rpassflags &= ~RP_VR;
|
||
}
|
||
|
||
void VK_R_RenderView (void)
|
||
{
|
||
extern unsigned int r_viewcontents;
|
||
struct vk_rendertarg *rt, *rtscreen = vk.rendertarg;
|
||
extern cvar_t r_fxaa;
|
||
extern cvar_t r_renderscale, r_postprocshader;
|
||
float renderscale = r_renderscale.value;
|
||
shader_t *custompostproc;
|
||
|
||
if (r_norefresh.value || !vid.fbpwidth || !vid.fbpwidth)
|
||
{
|
||
VK_Set2D ();
|
||
return;
|
||
}
|
||
|
||
VKBE_Set2D(false);
|
||
|
||
Surf_SetupFrame();
|
||
|
||
if (vid.vr && vid.vr->Render(VK_R_RenderEye))
|
||
{
|
||
VK_Set2D ();
|
||
return;
|
||
}
|
||
|
||
//check if we can do underwater warp
|
||
if (cls.protocol != CP_QUAKE2) //quake2 tells us directly
|
||
{
|
||
if (r_viewcontents & FTECONTENTS_FLUID)
|
||
r_refdef.flags |= RDF_UNDERWATER;
|
||
else
|
||
r_refdef.flags &= ~RDF_UNDERWATER;
|
||
}
|
||
if (r_refdef.flags & RDF_UNDERWATER)
|
||
{
|
||
extern cvar_t r_projection;
|
||
if (!r_waterwarp.value || r_projection.ival)
|
||
r_refdef.flags &= ~RDF_UNDERWATER; //no warp at all
|
||
else if (r_waterwarp.value > 0)
|
||
r_refdef.flags |= RDF_WATERWARP; //try fullscreen warp instead if we can
|
||
}
|
||
|
||
if (!r_refdef.globalfog.density)
|
||
{
|
||
int fogtype = ((r_refdef.flags & RDF_UNDERWATER) && cl.fog[FOGTYPE_WATER].density)?FOGTYPE_WATER:FOGTYPE_AIR;
|
||
CL_BlendFog(&r_refdef.globalfog, &cl.oldfog[fogtype], realtime, &cl.fog[fogtype]);
|
||
r_refdef.globalfog.density /= 64; //FIXME
|
||
}
|
||
|
||
custompostproc = NULL;
|
||
if (r_refdef.flags & RDF_NOWORLDMODEL)
|
||
renderscale = 1; //with no worldmodel, this is probably meant to be transparent so make sure that there's no post-proc stuff messing up transparencies.
|
||
else
|
||
{
|
||
if (*r_postprocshader.string)
|
||
{
|
||
custompostproc = R_RegisterCustom(NULL, r_postprocshader.string, SUF_NONE, NULL, NULL);
|
||
if (custompostproc)
|
||
r_refdef.flags |= RDF_CUSTOMPOSTPROC;
|
||
}
|
||
|
||
if (r_fxaa.ival) //overlays will have problems.
|
||
r_refdef.flags |= RDF_ANTIALIAS;
|
||
|
||
if (R_CanBloom())
|
||
r_refdef.flags |= RDF_BLOOM;
|
||
|
||
if (vid_hardwaregamma.ival == 4 && (v_gamma.value!=1||v_contrast.value!=1||v_contrastboost.value!=1||v_brightness.value!=0))
|
||
r_refdef.flags |= RDF_SCENEGAMMA;
|
||
}
|
||
|
||
// if (vk.multisamplebits != VK_SAMPLE_COUNT_1_BIT) //these are unsupported right now.
|
||
// r_refdef.flags &= ~(RDF_CUSTOMPOSTPROC|RDF_ANTIALIAS|RDF_BLOOM);
|
||
|
||
//
|
||
// figure out the viewport
|
||
//
|
||
{
|
||
int x = r_refdef.vrect.x * vid.pixelwidth/(int)vid.width;
|
||
int x2 = (r_refdef.vrect.x + r_refdef.vrect.width) * vid.pixelwidth/(int)vid.width;
|
||
int y = (r_refdef.vrect.y) * vid.pixelheight/(int)vid.height;
|
||
int y2 = ((int)(r_refdef.vrect.y + r_refdef.vrect.height)) * vid.pixelheight/(int)vid.height;
|
||
|
||
// fudge around because of frac screen scale
|
||
if (x > 0)
|
||
x--;
|
||
if (x2 < vid.pixelwidth)
|
||
x2++;
|
||
if (y < 0)
|
||
y--;
|
||
if (y2 < vid.pixelheight)
|
||
y2++;
|
||
|
||
r_refdef.pxrect.x = x;
|
||
r_refdef.pxrect.y = y;
|
||
r_refdef.pxrect.width = x2 - x;
|
||
r_refdef.pxrect.height = y2 - y;
|
||
r_refdef.pxrect.maxheight = vid.pixelheight;
|
||
}
|
||
|
||
if (renderscale != 1.0 || vk.multisamplebits != VK_SAMPLE_COUNT_1_BIT)
|
||
{
|
||
r_refdef.flags |= RDF_RENDERSCALE;
|
||
if (renderscale < 0)
|
||
renderscale *= -1;
|
||
r_refdef.pxrect.width *= renderscale;
|
||
r_refdef.pxrect.height *= renderscale;
|
||
r_refdef.pxrect.maxheight = r_refdef.pxrect.height;
|
||
}
|
||
|
||
if (r_refdef.pxrect.width <= 0 || r_refdef.pxrect.height <= 0)
|
||
return; //you're not allowed to do that, dude.
|
||
|
||
//FIXME: VF_RT_*
|
||
//FIXME: if we're meant to be using msaa, render the scene to an msaa target and then resolve.
|
||
|
||
postproc_buf = 0;
|
||
if (r_refdef.flags & (RDF_ALLPOSTPROC|RDF_RENDERSCALE|RDF_SCENEGAMMA))
|
||
{
|
||
r_refdef.pxrect.x = 0;
|
||
r_refdef.pxrect.y = 0;
|
||
rt = &postproc[postproc_buf++%countof(postproc)];
|
||
rt->rpassflags = 0;
|
||
if (vk.multisamplebits!=VK_SAMPLE_COUNT_1_BIT)
|
||
rt->rpassflags |= RP_MULTISAMPLE;
|
||
if (r_refdef.flags&RDF_SCENEGAMMA) //if we're doing scenegamma here, use an fp16 target for extra precision
|
||
rt->rpassflags |= RP_FP16;
|
||
VKBE_RT_Gen(rt, NULL, r_refdef.pxrect.width, r_refdef.pxrect.height, false, (r_renderscale.value < 0)?RT_IMAGEFLAGS-IF_LINEAR+IF_NEAREST:RT_IMAGEFLAGS);
|
||
}
|
||
else
|
||
rt = rtscreen;
|
||
|
||
if (!(r_refdef.flags & RDF_NOWORLDMODEL) && VK_R_RenderScene_Cubemap(rt))
|
||
{
|
||
}
|
||
else
|
||
{
|
||
VK_SetupViewPortProjection(false, NULL, NULL);
|
||
|
||
if (rt != rtscreen)
|
||
VKBE_RT_Begin(rt);
|
||
else
|
||
{
|
||
VkViewport vp[1];
|
||
VkRect2D scissor[1];
|
||
vp[0].x = r_refdef.pxrect.x;
|
||
vp[0].y = r_refdef.pxrect.y;
|
||
vp[0].width = r_refdef.pxrect.width;
|
||
vp[0].height = r_refdef.pxrect.height;
|
||
vp[0].minDepth = 0.0;
|
||
vp[0].maxDepth = 1.0;
|
||
scissor[0].offset.x = r_refdef.pxrect.x;
|
||
scissor[0].offset.y = r_refdef.pxrect.y;
|
||
scissor[0].extent.width = r_refdef.pxrect.width;
|
||
scissor[0].extent.height = r_refdef.pxrect.height;
|
||
vkCmdSetViewport(vk.rendertarg->cbuf, 0, countof(vp), vp);
|
||
vkCmdSetScissor(vk.rendertarg->cbuf, 0, countof(scissor), scissor);
|
||
}
|
||
|
||
if (!vk.rendertarg->depthcleared)
|
||
{
|
||
VkClearAttachment clr;
|
||
VkClearRect rect;
|
||
clr.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT;
|
||
clr.clearValue.depthStencil.depth = 1;
|
||
clr.clearValue.depthStencil.stencil = 0;
|
||
clr.colorAttachment = 1;
|
||
rect.rect.offset.x = r_refdef.pxrect.x;
|
||
rect.rect.offset.y = r_refdef.pxrect.y;
|
||
rect.rect.extent.width = r_refdef.pxrect.width;
|
||
rect.rect.extent.height = r_refdef.pxrect.height;
|
||
rect.layerCount = 1;
|
||
rect.baseArrayLayer = 0;
|
||
vkCmdClearAttachments(vk.rendertarg->cbuf, 1, &clr, 1, &rect);
|
||
vk.rendertarg->depthcleared = true;
|
||
}
|
||
|
||
VKBE_SelectEntity(&r_worldentity);
|
||
|
||
R_SetFrustum (r_refdef.m_projection_std, r_refdef.m_view);
|
||
RQ_BeginFrame();
|
||
if (!(r_refdef.flags & RDF_NOWORLDMODEL))
|
||
{
|
||
if (cl.worldmodel)
|
||
P_DrawParticles ();
|
||
}
|
||
Surf_DrawWorld();
|
||
RQ_RenderBatchClear();
|
||
|
||
vk.rendertarg->depthcleared = false;
|
||
|
||
VK_Set2D ();
|
||
|
||
if (rt != rtscreen)
|
||
VKBE_RT_End(rt);
|
||
}
|
||
|
||
if (r_refdef.flags & RDF_ALLPOSTPROC)
|
||
{
|
||
if (!vk.scenepp_waterwarp)
|
||
VK_Init_PostProc();
|
||
//FIXME: chain renderpasses as required.
|
||
|
||
if (r_refdef.flags & RDF_SCENEGAMMA)
|
||
{
|
||
shader_t *s = R_RegisterShader("fte_scenegamma", 0,
|
||
"{\n"
|
||
"program defaultgammacb\n"
|
||
"affine\n"
|
||
"{\n"
|
||
"map $sourcecolour\n"
|
||
"nodepthtest\n"
|
||
"}\n"
|
||
"}\n"
|
||
);
|
||
|
||
r_refdef.flags &= ~RDF_SCENEGAMMA;
|
||
vk.sourcecolour = &rt->q_colour;
|
||
if (r_refdef.flags & RDF_ALLPOSTPROC)
|
||
{
|
||
rt = &postproc[postproc_buf++];
|
||
rt->rpassflags = 0;
|
||
VKBE_RT_Gen(rt, NULL, 320, 200, false, RT_IMAGEFLAGS);
|
||
}
|
||
else
|
||
rt = rtscreen;
|
||
if (rt != rtscreen)
|
||
VKBE_RT_Begin(rt);
|
||
R2D_ImageColours (v_gammainverted.ival?v_gamma.value:(1/v_gamma.value), v_contrast.value, v_brightness.value, v_contrastboost.value);
|
||
R2D_Image(r_refdef.vrect.x, r_refdef.vrect.y, r_refdef.vrect.width, r_refdef.vrect.height, 0, 0, 1, 1, s);
|
||
R2D_ImageColours (1, 1, 1, 1);
|
||
R2D_Flush();
|
||
if (rt != rtscreen)
|
||
VKBE_RT_End(rt);
|
||
}
|
||
|
||
if (r_refdef.flags & RDF_WATERWARP)
|
||
{
|
||
r_refdef.flags &= ~RDF_WATERWARP;
|
||
vk.sourcecolour = &rt->q_colour;
|
||
if (r_refdef.flags & RDF_ALLPOSTPROC)
|
||
{
|
||
rt = &postproc[postproc_buf++];
|
||
rt->rpassflags = 0;
|
||
VKBE_RT_Gen(rt, NULL, 320, 200, false, RT_IMAGEFLAGS);
|
||
}
|
||
else
|
||
rt = rtscreen;
|
||
if (rt != rtscreen)
|
||
VKBE_RT_Begin(rt);
|
||
R2D_Image(r_refdef.vrect.x, r_refdef.vrect.y, r_refdef.vrect.width, r_refdef.vrect.height, 0, 0, 1, 1, vk.scenepp_waterwarp);
|
||
R2D_Flush();
|
||
if (rt != rtscreen)
|
||
VKBE_RT_End(rt);
|
||
}
|
||
if (r_refdef.flags & RDF_CUSTOMPOSTPROC)
|
||
{
|
||
r_refdef.flags &= ~RDF_CUSTOMPOSTPROC;
|
||
vk.sourcecolour = &rt->q_colour;
|
||
if (r_refdef.flags & RDF_ALLPOSTPROC)
|
||
{
|
||
rt = &postproc[postproc_buf++];
|
||
rt->rpassflags = 0;
|
||
VKBE_RT_Gen(rt, NULL, 320, 200, false, RT_IMAGEFLAGS);
|
||
}
|
||
else
|
||
rt = rtscreen;
|
||
if (rt != rtscreen)
|
||
VKBE_RT_Begin(rt);
|
||
R2D_Image(r_refdef.vrect.x, r_refdef.vrect.y, r_refdef.vrect.width, r_refdef.vrect.height, 0, 1, 1, 0, custompostproc);
|
||
R2D_Flush();
|
||
if (rt != rtscreen)
|
||
VKBE_RT_End(rt);
|
||
}
|
||
if (r_refdef.flags & RDF_ANTIALIAS)
|
||
{
|
||
r_refdef.flags &= ~RDF_ANTIALIAS;
|
||
R2D_ImageColours(rt->width, rt->height, 1, 1);
|
||
vk.sourcecolour = &rt->q_colour;
|
||
if (r_refdef.flags & RDF_ALLPOSTPROC)
|
||
{
|
||
rt = &postproc[postproc_buf++];
|
||
rt->rpassflags = 0;
|
||
VKBE_RT_Gen(rt, NULL, 320, 200, false, RT_IMAGEFLAGS);
|
||
}
|
||
else
|
||
rt = rtscreen;
|
||
if (rt != rtscreen)
|
||
VKBE_RT_Begin(rt);
|
||
R2D_Image(r_refdef.vrect.x, r_refdef.vrect.y, r_refdef.vrect.width, r_refdef.vrect.height, 0, 1, 1, 0, vk.scenepp_antialias);
|
||
R2D_ImageColours(1, 1, 1, 1);
|
||
R2D_Flush();
|
||
if (rt != rtscreen)
|
||
VKBE_RT_End(rt);
|
||
}
|
||
if (r_refdef.flags & RDF_BLOOM)
|
||
{
|
||
VK_R_BloomBlend(&rt->q_colour, r_refdef.vrect.x, r_refdef.vrect.y, r_refdef.vrect.width, r_refdef.vrect.height);
|
||
rt = rtscreen;
|
||
}
|
||
}
|
||
else if (r_refdef.flags & RDF_RENDERSCALE)
|
||
{
|
||
if (!vk.scenepp_rescale)
|
||
vk.scenepp_rescale = R_RegisterShader("fte_rescaler", 0,
|
||
"{\n"
|
||
"program default2d\n"
|
||
"{\n"
|
||
"map $sourcecolour\n"
|
||
"}\n"
|
||
"}\n"
|
||
);
|
||
vk.sourcecolour = &rt->q_colour;
|
||
rt = rtscreen;
|
||
R2D_Image(r_refdef.vrect.x, r_refdef.vrect.y, r_refdef.vrect.width, r_refdef.vrect.height, 0, 0, 1, 1, vk.scenepp_rescale);
|
||
R2D_Flush();
|
||
}
|
||
vk.sourcecolour = r_nulltex;
|
||
}
|
||
|
||
|
||
typedef struct
|
||
{
|
||
uint32_t imageformat;
|
||
uint32_t imagestride;
|
||
uint32_t imagewidth;
|
||
uint32_t imageheight;
|
||
VkBuffer buffer;
|
||
size_t memsize;
|
||
VkDeviceMemory memory;
|
||
void (*gotrgbdata) (void *rgbdata, intptr_t bytestride, size_t width, size_t height, enum uploadfmt fmt);
|
||
} vkscreencapture_t;
|
||
|
||
static void VKVID_CopiedRGBData (void*ctx)
|
||
{ //some fence got hit, we did our copy, data is now cpu-visible, cache-willing.
|
||
vkscreencapture_t *capt = ctx;
|
||
void *imgdata;
|
||
VkAssert(vkMapMemory(vk.device, capt->memory, 0, capt->memsize, 0, &imgdata));
|
||
capt->gotrgbdata(imgdata, capt->imagestride, capt->imagewidth, capt->imageheight, capt->imageformat);
|
||
vkUnmapMemory(vk.device, capt->memory);
|
||
vkDestroyBuffer(vk.device, capt->buffer, vkallocationcb);
|
||
vkFreeMemory(vk.device, capt->memory, vkallocationcb);
|
||
}
|
||
void VKVID_QueueGetRGBData (void (*gotrgbdata) (void *rgbdata, intptr_t bytestride, size_t width, size_t height, enum uploadfmt fmt))
|
||
{
|
||
//should be half way through rendering
|
||
vkscreencapture_t *capt;
|
||
|
||
VkBufferImageCopy icpy;
|
||
|
||
VkMemoryRequirements mem_reqs;
|
||
VkMemoryAllocateInfo memAllocInfo = {VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO};
|
||
VkBufferCreateInfo bci = {VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO};
|
||
|
||
|
||
if (!VK_SCR_GrabBackBuffer())
|
||
return;
|
||
|
||
if (!vk.frame->backbuf->colour.width || !vk.frame->backbuf->colour.height)
|
||
return; //erm, some kind of error?
|
||
|
||
capt = VK_AtFrameEnd(VKVID_CopiedRGBData, NULL, sizeof(*capt));
|
||
capt->gotrgbdata = gotrgbdata;
|
||
|
||
//FIXME: vkCmdBlitImage the image to convert it from half-float or whatever to a format that our screenshot etc code can cope with.
|
||
capt->imageformat = TF_BGRA32;
|
||
capt->imagestride = vk.frame->backbuf->colour.width*4; //vulkan is top-down, so this should be positive.
|
||
capt->imagewidth = vk.frame->backbuf->colour.width;
|
||
capt->imageheight = vk.frame->backbuf->colour.height;
|
||
|
||
bci.flags = 0;
|
||
bci.size = capt->memsize = capt->imagewidth*capt->imageheight*4;
|
||
bci.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT;
|
||
bci.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
||
bci.queueFamilyIndexCount = 0;
|
||
bci.pQueueFamilyIndices = NULL;
|
||
|
||
VkAssert(vkCreateBuffer(vk.device, &bci, vkallocationcb, &capt->buffer));
|
||
vkGetBufferMemoryRequirements(vk.device, capt->buffer, &mem_reqs);
|
||
memAllocInfo.allocationSize = mem_reqs.size;
|
||
memAllocInfo.memoryTypeIndex = vk_find_memory_try(mem_reqs.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT|VK_MEMORY_PROPERTY_HOST_CACHED_BIT);
|
||
if (memAllocInfo.memoryTypeIndex == ~0u)
|
||
memAllocInfo.memoryTypeIndex = vk_find_memory_require(mem_reqs.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT);
|
||
VkAssert(vkAllocateMemory(vk.device, &memAllocInfo, vkallocationcb, &capt->memory));
|
||
VkAssert(vkBindBufferMemory(vk.device, capt->buffer, capt->memory, 0));
|
||
|
||
set_image_layout(vk.rendertarg->cbuf, vk.frame->backbuf->colour.image, VK_IMAGE_ASPECT_COLOR_BIT,
|
||
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
|
||
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_ACCESS_TRANSFER_READ_BIT, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT);
|
||
|
||
icpy.bufferOffset = 0;
|
||
icpy.bufferRowLength = 0; //packed
|
||
icpy.bufferImageHeight = 0; //packed
|
||
icpy.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||
icpy.imageSubresource.mipLevel = 0;
|
||
icpy.imageSubresource.baseArrayLayer = 0;
|
||
icpy.imageSubresource.layerCount = 1;
|
||
icpy.imageOffset.x = 0;
|
||
icpy.imageOffset.y = 0;
|
||
icpy.imageOffset.z = 0;
|
||
icpy.imageExtent.width = capt->imagewidth;
|
||
icpy.imageExtent.height = capt->imageheight;
|
||
icpy.imageExtent.depth = 1;
|
||
|
||
vkCmdCopyImageToBuffer(vk.rendertarg->cbuf, vk.frame->backbuf->colour.image, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, capt->buffer, 1, &icpy);
|
||
|
||
set_image_layout(vk.rendertarg->cbuf, vk.frame->backbuf->colour.image, VK_IMAGE_ASPECT_COLOR_BIT,
|
||
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_ACCESS_TRANSFER_READ_BIT, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
|
||
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT);
|
||
}
|
||
|
||
char *VKVID_GetRGBInfo (int *bytestride, int *truevidwidth, int *truevidheight, enum uploadfmt *fmt)
|
||
{
|
||
//in order to deal with various backbuffer formats (like half-float) etc, we play safe and blit the framebuffer to a safe format.
|
||
//we then transfer that into a buffer that we can then directly read.
|
||
//and then we allocate a C buffer that we then copy it into...
|
||
//so yeah, 3 copies. life sucks.
|
||
//blit requires support for VK_IMAGE_USAGE_TRANSFER_DST_BIT on our image, which means we need optimal, which means we can't directly map it, which means we need the buffer copy too.
|
||
//this might be relaxed on mobile, but who really takes screenshots on mobiles anyway?!? anyway, video capture shouldn't be using this either way so top performance isn't a concern
|
||
if (VK_SCR_GrabBackBuffer())
|
||
{
|
||
VkImageLayout framebufferlayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;//vk.frame->backbuf->colour.layout;
|
||
|
||
void *imgdata, *outdata;
|
||
struct vk_fencework *fence = VK_FencedBegin(NULL, 0);
|
||
VkImage tempimage;
|
||
VkDeviceMemory tempmemory;
|
||
VkBufferCreateInfo bci = {VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO};
|
||
VkBuffer tempbuffer;
|
||
VkDeviceMemory tempbufmemory;
|
||
VkMemoryRequirements mem_reqs;
|
||
VkMemoryAllocateInfo memAllocInfo = {VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO};
|
||
VkImageCreateInfo ici = {VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO};
|
||
//VkFormatProperties vkfmt;
|
||
|
||
ici.flags = 0;
|
||
ici.imageType = VK_IMAGE_TYPE_2D;
|
||
/*vkGetPhysicalDeviceFormatProperties(vk.gpu, VK_FORMAT_B8G8R8_UNORM, &vkfmt);
|
||
if ((vkfmt.optimalTilingFeatures & VK_FORMAT_FEATURE_BLIT_DST_BIT) && (vkfmt.optimalTilingFeatures & VK_FORMAT_FEATURE_TRANSFER_SRC_BIT_KHR))
|
||
{ //if we can do BGR, then use it, because that's what most PC file formats use, like tga.
|
||
//we don't really want alpha data anyway.
|
||
if (vid.flags & VID_SRGB_FB)
|
||
ici.format = VK_FORMAT_B8G8R8_SRGB;
|
||
else
|
||
ici.format = VK_FORMAT_B8G8R8_UNORM;
|
||
}
|
||
else*/
|
||
{ //otherwise lets just get bgra data.
|
||
if (vid.flags & VID_SRGB_FB)
|
||
ici.format = VK_FORMAT_B8G8R8A8_SRGB;
|
||
else
|
||
ici.format = VK_FORMAT_B8G8R8A8_UNORM;
|
||
}
|
||
ici.extent.width = vid.pixelwidth;
|
||
ici.extent.height = vid.pixelheight;
|
||
ici.extent.depth = 1;
|
||
ici.mipLevels = 1;
|
||
ici.arrayLayers = 1;
|
||
ici.samples = VK_SAMPLE_COUNT_1_BIT;
|
||
ici.tiling = VK_IMAGE_TILING_OPTIMAL;
|
||
ici.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT|VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
|
||
ici.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
||
ici.queueFamilyIndexCount = 0;
|
||
ici.pQueueFamilyIndices = NULL;
|
||
ici.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||
VkAssert(vkCreateImage(vk.device, &ici, vkallocationcb, &tempimage));
|
||
DebugSetName(VK_OBJECT_TYPE_IMAGE, (uint64_t)tempimage, "VKVID_GetRGBInfo staging");
|
||
vkGetImageMemoryRequirements(vk.device, tempimage, &mem_reqs);
|
||
memAllocInfo.allocationSize = mem_reqs.size;
|
||
memAllocInfo.memoryTypeIndex = vk_find_memory_require(mem_reqs.memoryTypeBits, 0);
|
||
VkAssert(vkAllocateMemory(vk.device, &memAllocInfo, vkallocationcb, &tempmemory));
|
||
VkAssert(vkBindImageMemory(vk.device, tempimage, tempmemory, 0));
|
||
|
||
bci.flags = 0;
|
||
bci.size = vid.pixelwidth*vid.pixelheight*4;
|
||
bci.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT;
|
||
bci.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
||
bci.queueFamilyIndexCount = 0;
|
||
bci.pQueueFamilyIndices = NULL;
|
||
|
||
VkAssert(vkCreateBuffer(vk.device, &bci, vkallocationcb, &tempbuffer));
|
||
vkGetBufferMemoryRequirements(vk.device, tempbuffer, &mem_reqs);
|
||
memAllocInfo.allocationSize = mem_reqs.size;
|
||
memAllocInfo.memoryTypeIndex = vk_find_memory_try(mem_reqs.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT|VK_MEMORY_PROPERTY_HOST_CACHED_BIT);
|
||
if (memAllocInfo.memoryTypeIndex == ~0u)
|
||
memAllocInfo.memoryTypeIndex = vk_find_memory_require(mem_reqs.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT);
|
||
VkAssert(vkAllocateMemory(vk.device, &memAllocInfo, vkallocationcb, &tempbufmemory));
|
||
VkAssert(vkBindBufferMemory(vk.device, tempbuffer, tempbufmemory, 0));
|
||
|
||
|
||
set_image_layout(fence->cbuf, vk.frame->backbuf->colour.image, VK_IMAGE_ASPECT_COLOR_BIT,
|
||
framebufferlayout, VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
|
||
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_ACCESS_TRANSFER_READ_BIT, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT);
|
||
set_image_layout(fence->cbuf, tempimage, VK_IMAGE_ASPECT_COLOR_BIT,
|
||
VK_IMAGE_LAYOUT_UNDEFINED, 0, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
|
||
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_ACCESS_TRANSFER_WRITE_BIT, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT);
|
||
{
|
||
VkImageBlit iblt;
|
||
iblt.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||
iblt.srcSubresource.mipLevel = 0;
|
||
iblt.srcSubresource.baseArrayLayer = 0;
|
||
iblt.srcSubresource.layerCount = 1;
|
||
iblt.srcOffsets[0].x = 0;
|
||
iblt.srcOffsets[0].y = 0;
|
||
iblt.srcOffsets[0].z = 0;
|
||
iblt.srcOffsets[1].x = vid.pixelwidth;
|
||
iblt.srcOffsets[1].y = vid.pixelheight;
|
||
iblt.srcOffsets[1].z = 1;
|
||
iblt.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||
iblt.dstSubresource.mipLevel = 0;
|
||
iblt.dstSubresource.baseArrayLayer = 0;
|
||
iblt.dstSubresource.layerCount = 1;
|
||
iblt.dstOffsets[0].x = 0;
|
||
iblt.dstOffsets[0].y = 0;
|
||
iblt.dstOffsets[0].z = 0;
|
||
iblt.dstOffsets[1].x = vid.pixelwidth;
|
||
iblt.dstOffsets[1].y = vid.pixelheight;
|
||
iblt.dstOffsets[1].z = 1;
|
||
|
||
vkCmdBlitImage(fence->cbuf, vk.frame->backbuf->colour.image, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, tempimage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &iblt, VK_FILTER_LINEAR);
|
||
}
|
||
set_image_layout(fence->cbuf, vk.frame->backbuf->colour.image, VK_IMAGE_ASPECT_COLOR_BIT,
|
||
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_ACCESS_TRANSFER_READ_BIT, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
|
||
framebufferlayout, VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT);
|
||
set_image_layout(fence->cbuf, tempimage, VK_IMAGE_ASPECT_COLOR_BIT,
|
||
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_ACCESS_TRANSFER_WRITE_BIT, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
|
||
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_ACCESS_TRANSFER_READ_BIT, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT);
|
||
|
||
{
|
||
VkBufferImageCopy icpy;
|
||
icpy.bufferOffset = 0;
|
||
icpy.bufferRowLength = 0; //packed
|
||
icpy.bufferImageHeight = 0; //packed
|
||
icpy.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||
icpy.imageSubresource.mipLevel = 0;
|
||
icpy.imageSubresource.baseArrayLayer = 0;
|
||
icpy.imageSubresource.layerCount = 1;
|
||
icpy.imageOffset.x = 0;
|
||
icpy.imageOffset.y = 0;
|
||
icpy.imageOffset.z = 0;
|
||
icpy.imageExtent.width = ici.extent.width;
|
||
icpy.imageExtent.height = ici.extent.height;
|
||
icpy.imageExtent.depth = 1;
|
||
|
||
vkCmdCopyImageToBuffer(fence->cbuf, tempimage, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, tempbuffer, 1, &icpy);
|
||
}
|
||
|
||
VK_FencedSync(fence);
|
||
|
||
outdata = BZ_Malloc(4*ici.extent.width*ici.extent.height);
|
||
if (ici.format == VK_FORMAT_B8G8R8_SRGB || ici.format == VK_FORMAT_B8G8R8_UNORM)
|
||
*fmt = PTI_BGR8;
|
||
else if (ici.format == VK_FORMAT_R8G8B8_SRGB || ici.format == VK_FORMAT_R8G8B8_UNORM)
|
||
*fmt = PTI_RGB8;
|
||
else if (ici.format == VK_FORMAT_R8G8B8A8_SRGB || ici.format == VK_FORMAT_R8G8B8A8_UNORM)
|
||
*fmt = PTI_RGBA8;
|
||
else
|
||
*fmt = PTI_BGRA8;
|
||
*bytestride = ici.extent.width*4;
|
||
*truevidwidth = ici.extent.width;
|
||
*truevidheight = ici.extent.height;
|
||
|
||
VkAssert(vkMapMemory(vk.device, tempbufmemory, 0, 4*ici.extent.width*ici.extent.height, 0, &imgdata));
|
||
memcpy(outdata, imgdata, 4*ici.extent.width*ici.extent.height);
|
||
vkUnmapMemory(vk.device, tempbufmemory);
|
||
|
||
vkDestroyImage(vk.device, tempimage, vkallocationcb);
|
||
vkFreeMemory(vk.device, tempmemory, vkallocationcb);
|
||
|
||
vkDestroyBuffer(vk.device, tempbuffer, vkallocationcb);
|
||
vkFreeMemory(vk.device, tempbufmemory, vkallocationcb);
|
||
|
||
return outdata;
|
||
}
|
||
return NULL;
|
||
}
|
||
|
||
static void VK_PaintScreen(void)
|
||
{
|
||
qboolean nohud;
|
||
qboolean noworld;
|
||
|
||
|
||
vid.fbvwidth = vid.width;
|
||
vid.fbvheight = vid.height;
|
||
vid.fbpwidth = vid.pixelwidth;
|
||
vid.fbpheight = vid.pixelheight;
|
||
|
||
r_refdef.pxrect.x = 0;
|
||
r_refdef.pxrect.y = 0;
|
||
r_refdef.pxrect.width = vid.fbpwidth;
|
||
r_refdef.pxrect.height = vid.fbpheight;
|
||
r_refdef.pxrect.maxheight = vid.pixelheight;
|
||
|
||
vid.numpages = vk.backbuf_count + 1;
|
||
|
||
R2D_Font_Changed();
|
||
|
||
VK_Set2D ();
|
||
|
||
Shader_DoReload();
|
||
|
||
if (scr_disabled_for_loading)
|
||
{
|
||
extern float scr_disabled_time;
|
||
if (Sys_DoubleTime() - scr_disabled_time > 60 || !Key_Dest_Has(~kdm_game))
|
||
{
|
||
//FIXME: instead of reenabling the screen, we should just draw the relevent things skipping only the game.
|
||
scr_disabled_for_loading = false;
|
||
}
|
||
else
|
||
{
|
||
// scr_drawloading = true;
|
||
SCR_DrawLoading (true);
|
||
// scr_drawloading = false;
|
||
return;
|
||
}
|
||
}
|
||
|
||
/* if (!scr_initialized || !con_initialized)
|
||
{
|
||
RSpeedEnd(RSPEED_TOTALREFRESH);
|
||
return; // not initialized yet
|
||
}
|
||
*/
|
||
|
||
#ifdef TEXTEDITOR
|
||
if (editormodal)
|
||
{
|
||
Editor_Draw();
|
||
V_UpdatePalette (false);
|
||
#if defined(_WIN32) && defined(GLQUAKE)
|
||
Media_RecordFrame();
|
||
#endif
|
||
R2D_BrightenScreen();
|
||
|
||
if (key_dest_mask & kdm_console)
|
||
Con_DrawConsole(vid.height/2, false);
|
||
else
|
||
Con_DrawConsole(0, false);
|
||
// SCR_DrawCursor();
|
||
return;
|
||
}
|
||
#endif
|
||
|
||
//
|
||
// do 3D refresh drawing, and then update the screen
|
||
//
|
||
SCR_SetUpToDrawConsole ();
|
||
|
||
noworld = false;
|
||
nohud = false;
|
||
|
||
if (topmenu && topmenu->isopaque)
|
||
nohud = true;
|
||
#ifdef VM_CG
|
||
else if (CG_Refresh())
|
||
nohud = true;
|
||
#endif
|
||
#ifdef CSQC_DAT
|
||
else if (CSQC_DrawView())
|
||
nohud = true;
|
||
#endif
|
||
else
|
||
{
|
||
if (r_worldentity.model && cls.state == ca_active)
|
||
V_RenderView (nohud);
|
||
else
|
||
{
|
||
noworld = true;
|
||
}
|
||
}
|
||
|
||
scr_con_forcedraw = false;
|
||
if (noworld)
|
||
{
|
||
//draw the levelshot or the conback fullscreen
|
||
if (R2D_DrawLevelshot())
|
||
;
|
||
else if (scr_con_current != vid.height)
|
||
{
|
||
#ifdef HAVE_LEGACY
|
||
extern cvar_t dpcompat_console;
|
||
if (dpcompat_console.ival)
|
||
{
|
||
R2D_ImageColours(0,0,0,1);
|
||
R2D_FillBlock(0, 0, vid.width, vid.height);
|
||
R2D_ImageColours(1,1,1,1);
|
||
}
|
||
else
|
||
#endif
|
||
R2D_ConsoleBackground(0, vid.height, true);
|
||
}
|
||
else
|
||
scr_con_forcedraw = true;
|
||
|
||
nohud = true;
|
||
}
|
||
|
||
SCR_DrawTwoDimensional(nohud);
|
||
|
||
V_UpdatePalette (false);
|
||
R2D_BrightenScreen();
|
||
|
||
#if defined(_WIN32) && defined(GLQUAKE)
|
||
Media_RecordFrame();
|
||
#endif
|
||
|
||
RSpeedShow();
|
||
}
|
||
|
||
VkCommandBuffer VK_AllocFrameCBuf(void)
|
||
{
|
||
struct vkframe *frame = vk.frame;
|
||
if (frame->numcbufs == frame->maxcbufs)
|
||
{
|
||
VkCommandBufferAllocateInfo cbai = {VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO};
|
||
|
||
frame->maxcbufs++;
|
||
frame->cbufs = BZ_Realloc(frame->cbufs, sizeof(*frame->cbufs)*frame->maxcbufs);
|
||
|
||
cbai.commandPool = vk.cmdpool;
|
||
cbai.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
|
||
cbai.commandBufferCount = frame->maxcbufs - frame->numcbufs;
|
||
VkAssert(vkAllocateCommandBuffers(vk.device, &cbai, frame->cbufs+frame->numcbufs));
|
||
DebugSetName(VK_OBJECT_TYPE_COMMAND_BUFFER, (uint64_t)frame->cbufs[frame->numcbufs], "VK_AllocFrameCBuf");
|
||
}
|
||
return frame->cbufs[frame->numcbufs++];
|
||
}
|
||
|
||
qboolean VK_SCR_GrabBackBuffer(void)
|
||
{
|
||
VkSemaphore sem;
|
||
RSpeedLocals();
|
||
|
||
if (vk.frame) //erk, we already have one...
|
||
return true;
|
||
|
||
|
||
RSpeedRemark();
|
||
|
||
VK_FencedCheck();
|
||
|
||
if (!vk.unusedframes)
|
||
{
|
||
struct vkframe *newframe = Z_Malloc(sizeof(*vk.frame));
|
||
VKBE_InitFramePools(newframe);
|
||
newframe->next = vk.unusedframes;
|
||
vk.unusedframes = newframe;
|
||
}
|
||
|
||
while (vk.acquirenext == vk.acquirelast)
|
||
{ //we're still waiting for the render thread to increment acquirelast.
|
||
//shouldn't really happen, but can if the gpu is slow.
|
||
if (vk.neednewswapchain)
|
||
{ //the render thread is is likely to have died... don't loop until infinity.
|
||
#ifdef MULTITHREAD
|
||
if (vk.submitthread)
|
||
{
|
||
//signal its condition, in case its sleeping, so we don't wait for infinity
|
||
Sys_LockConditional(vk.submitcondition);
|
||
Sys_ConditionSignal(vk.submitcondition);
|
||
Sys_UnlockConditional(vk.submitcondition);
|
||
|
||
//now wait+clean up the thread
|
||
Sys_WaitOnThread(vk.submitthread);
|
||
vk.submitthread = NULL;
|
||
}
|
||
#endif
|
||
return false;
|
||
}
|
||
Sys_Sleep(0); //o.O
|
||
#ifdef _WIN32
|
||
Sys_SendKeyEvents();
|
||
#endif
|
||
}
|
||
|
||
if (vk.acquirefences[vk.acquirenext%ACQUIRELIMIT] != VK_NULL_HANDLE)
|
||
{
|
||
//wait for the queued acquire to actually finish
|
||
if (vk_busywait.ival)
|
||
{ //busy wait, to try to get the highest fps possible
|
||
for (;;)
|
||
{
|
||
switch(vkGetFenceStatus(vk.device, vk.acquirefences[vk.acquirenext%ACQUIRELIMIT]))
|
||
{
|
||
case VK_SUCCESS:
|
||
break; //hurrah
|
||
case VK_NOT_READY:
|
||
continue; //keep going until its actually signaled. submission thread is probably just slow.
|
||
case VK_TIMEOUT:
|
||
continue; //erk? this isn't a documented result here.
|
||
case VK_ERROR_DEVICE_LOST:
|
||
Sys_Error("Vulkan device lost");
|
||
default:
|
||
return false;
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
//friendly wait
|
||
int failures = 0;
|
||
for(;;)
|
||
{
|
||
VkResult err = vkWaitForFences(vk.device, 1, &vk.acquirefences[vk.acquirenext%ACQUIRELIMIT], VK_FALSE, 1000000000);
|
||
|
||
if (err == VK_SUCCESS)
|
||
break;
|
||
else if (err == VK_TIMEOUT)
|
||
{
|
||
if (++failures == 5)
|
||
Sys_Error("waiting for fence for over 5 seconds. Assuming bug.");
|
||
continue;
|
||
}
|
||
else if (err == VK_ERROR_DEVICE_LOST)
|
||
Sys_Error("Vulkan device lost");
|
||
else if (err != VK_ERROR_OUT_OF_HOST_MEMORY && err != VK_ERROR_OUT_OF_DEVICE_MEMORY)
|
||
Sys_Error("vkWaitForFences returned unspecified result: %s", VK_VKErrorToString(err));
|
||
return false;
|
||
}
|
||
}
|
||
VkAssert(vkResetFences(vk.device, 1, &vk.acquirefences[vk.acquirenext%ACQUIRELIMIT]));
|
||
}
|
||
vk.bufferidx = vk.acquirebufferidx[vk.acquirenext%ACQUIRELIMIT];
|
||
|
||
sem = vk.acquiresemaphores[vk.acquirenext%ACQUIRELIMIT];
|
||
vk.acquirenext++;
|
||
|
||
//grab the first unused
|
||
Sys_LockConditional(vk.submitcondition);
|
||
vk.frame = vk.unusedframes;
|
||
vk.unusedframes = vk.frame->next;
|
||
vk.frame->next = NULL;
|
||
Sys_UnlockConditional(vk.submitcondition);
|
||
|
||
VkAssert(vkResetFences(vk.device, 1, &vk.frame->finishedfence));
|
||
|
||
vk.frame->backbuf = &vk.backbufs[vk.bufferidx];
|
||
vk.rendertarg = vk.frame->backbuf;
|
||
|
||
vk.frame->numcbufs = 0;
|
||
vk.rendertarg->cbuf = VK_AllocFrameCBuf();
|
||
vk.frame->acquiresemaphore = sem;
|
||
|
||
RSpeedEnd(RSPEED_SETUP);
|
||
|
||
|
||
|
||
|
||
|
||
|
||
{
|
||
VkCommandBufferBeginInfo begininf = {VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO};
|
||
VkCommandBufferInheritanceInfo inh = {VK_STRUCTURE_TYPE_COMMAND_BUFFER_INHERITANCE_INFO};
|
||
begininf.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
|
||
begininf.pInheritanceInfo = &inh;
|
||
inh.renderPass = VK_NULL_HANDLE; //unused
|
||
inh.subpass = 0; //unused
|
||
inh.framebuffer = VK_NULL_HANDLE; //unused
|
||
inh.occlusionQueryEnable = VK_FALSE;
|
||
inh.queryFlags = 0;
|
||
inh.pipelineStatistics = 0;
|
||
vkBeginCommandBuffer(vk.rendertarg->cbuf, &begininf);
|
||
}
|
||
|
||
VKBE_RestartFrame();
|
||
|
||
// VK_DebugFramerate();
|
||
|
||
// vkCmdWriteTimestamp(vk.frame->cbuf, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, querypool, vk.bufferidx*2+0);
|
||
|
||
if (!(vk.rendertarg->rpassflags & RP_PRESENTABLE))
|
||
{
|
||
VkImageMemoryBarrier imgbarrier = {VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER};
|
||
imgbarrier.pNext = NULL;
|
||
imgbarrier.srcAccessMask = 0;//VK_ACCESS_MEMORY_READ_BIT;
|
||
imgbarrier.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
|
||
imgbarrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;//vk.rendertarg->colour.layout; //'Alternately, oldLayout can be VK_IMAGE_LAYOUT_UNDEFINED, if the image's contents need not be preserved.'
|
||
imgbarrier.newLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
|
||
imgbarrier.image = vk.frame->backbuf->colour.image;
|
||
imgbarrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||
imgbarrier.subresourceRange.baseMipLevel = 0;
|
||
imgbarrier.subresourceRange.levelCount = 1;
|
||
imgbarrier.subresourceRange.baseArrayLayer = 0;
|
||
imgbarrier.subresourceRange.layerCount = 1;
|
||
imgbarrier.srcQueueFamilyIndex = vk.queuefam[1];
|
||
imgbarrier.dstQueueFamilyIndex = vk.queuefam[0];
|
||
if (vk.frame->backbuf->firstuse)
|
||
{
|
||
imgbarrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||
imgbarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||
imgbarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||
}
|
||
vk.rendertarg->colour.layout = imgbarrier.newLayout;
|
||
vkCmdPipelineBarrier(vk.rendertarg->cbuf, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, 0, 0, NULL, 0, NULL, 1, &imgbarrier);
|
||
}
|
||
{
|
||
VkImageMemoryBarrier imgbarrier = {VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER};
|
||
imgbarrier.pNext = NULL;
|
||
imgbarrier.srcAccessMask = 0;
|
||
imgbarrier.dstAccessMask = 0;//VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
|
||
imgbarrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||
imgbarrier.newLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
|
||
imgbarrier.image = vk.frame->backbuf->depth.image;
|
||
imgbarrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT;
|
||
imgbarrier.subresourceRange.baseMipLevel = 0;
|
||
imgbarrier.subresourceRange.levelCount = 1;
|
||
imgbarrier.subresourceRange.baseArrayLayer = 0;
|
||
imgbarrier.subresourceRange.layerCount = 1;
|
||
imgbarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||
imgbarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
|
||
vkCmdPipelineBarrier(vk.rendertarg->cbuf, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, 0, 0, NULL, 0, NULL, 1, &imgbarrier);
|
||
}
|
||
|
||
{
|
||
int rp = vk.frame->backbuf->rpassflags;
|
||
VkClearValue clearvalues[3];
|
||
extern cvar_t r_clear;
|
||
VkRenderPassBeginInfo rpbi = {VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO};
|
||
|
||
//attachments are: screen[1], depth[msbits], (screen[msbits])
|
||
|
||
clearvalues[0].color.float32[0] = !!(r_clear.ival & 1);
|
||
clearvalues[0].color.float32[1] = !!(r_clear.ival & 2);
|
||
clearvalues[0].color.float32[2] = !!(r_clear.ival & 4);
|
||
clearvalues[0].color.float32[3] = 1;
|
||
clearvalues[1].depthStencil.depth = 1.0;
|
||
clearvalues[1].depthStencil.stencil = 0;
|
||
|
||
if (rp & RP_MULTISAMPLE)
|
||
{
|
||
clearvalues[2].color.float32[0] = !!(r_clear.ival & 1);
|
||
clearvalues[2].color.float32[1] = !!(r_clear.ival & 2);
|
||
clearvalues[2].color.float32[2] = !!(r_clear.ival & 4);
|
||
clearvalues[2].color.float32[3] = 1;
|
||
rpbi.clearValueCount = 3;
|
||
}
|
||
else
|
||
rpbi.clearValueCount = 2;
|
||
|
||
if (r_clear.ival || vk.frame->backbuf->firstuse)
|
||
rpbi.renderPass = VK_GetRenderPass(RP_FULLCLEAR|rp);
|
||
else
|
||
rpbi.renderPass = VK_GetRenderPass(RP_DEPTHCLEAR|rp);
|
||
rpbi.framebuffer = vk.frame->backbuf->framebuffer;
|
||
rpbi.renderArea.offset.x = 0;
|
||
rpbi.renderArea.offset.y = 0;
|
||
rpbi.renderArea.extent.width = vk.frame->backbuf->colour.width;
|
||
rpbi.renderArea.extent.height = vk.frame->backbuf->colour.height;
|
||
rpbi.pClearValues = clearvalues;
|
||
vkCmdBeginRenderPass(vk.rendertarg->cbuf, &rpbi, VK_SUBPASS_CONTENTS_INLINE);
|
||
|
||
vk.frame->backbuf->width = rpbi.renderArea.extent.width;
|
||
vk.frame->backbuf->height = rpbi.renderArea.extent.height;
|
||
|
||
rpbi.clearValueCount = 0;
|
||
rpbi.pClearValues = NULL;
|
||
rpbi.renderPass = VK_GetRenderPass(RP_RESUME|rp);
|
||
vk.rendertarg->restartinfo = rpbi;
|
||
vk.rendertarg->depthcleared = true;
|
||
}
|
||
vk.frame->backbuf->firstuse = false;
|
||
return true;
|
||
}
|
||
|
||
struct vk_presented
|
||
{
|
||
struct vk_fencework fw;
|
||
struct vkframe *frame;
|
||
};
|
||
void VK_Presented(void *fw)
|
||
{
|
||
struct vk_presented *pres = fw;
|
||
struct vkframe *frame = pres->frame;
|
||
pres->fw.fence = VK_NULL_HANDLE; //don't allow that to be freed.
|
||
|
||
while(frame->frameendjobs)
|
||
{
|
||
struct vk_frameend *job = frame->frameendjobs;
|
||
frame->frameendjobs = job->next;
|
||
job->FrameEnded(job+1);
|
||
Z_Free(job);
|
||
}
|
||
|
||
frame->next = vk.unusedframes;
|
||
vk.unusedframes = frame;
|
||
}
|
||
|
||
#if 0
|
||
void VK_DebugFramerate(void)
|
||
{
|
||
static double lastupdatetime;
|
||
static double lastsystemtime;
|
||
double t;
|
||
extern int fps_count;
|
||
float lastfps;
|
||
|
||
float frametime;
|
||
|
||
t = Sys_DoubleTime();
|
||
if ((t - lastupdatetime) >= 1.0)
|
||
{
|
||
lastfps = fps_count/(t - lastupdatetime);
|
||
fps_count = 0;
|
||
lastupdatetime = t;
|
||
|
||
OutputDebugStringA(va("%g fps\n", lastfps));
|
||
}
|
||
frametime = t - lastsystemtime;
|
||
lastsystemtime = t;
|
||
}
|
||
#endif
|
||
|
||
qboolean VK_SCR_UpdateScreen (void)
|
||
{
|
||
VkImageLayout fblayout;
|
||
|
||
VK_FencedCheck();
|
||
|
||
//a few cvars need some extra work if they're changed
|
||
if ((vk.allowsubmissionthread && vk_submissionthread.modified) || vid_vsync.modified || vk_waitfence.modified || vid_triplebuffer.modified || vid_srgb.modified || vid_multisample.modified)
|
||
vk.neednewswapchain = true;
|
||
|
||
if (vk.devicelost)
|
||
{ //vkQueueSubmit returning vk_error_device_lost means we give up and try resetting everything.
|
||
//if someone's installing new drivers then wait a little time before reloading everything, in the hope that any other dependant files got copied. or something.
|
||
//fixme: don't allow this to be spammed...
|
||
Sys_Sleep(5);
|
||
Con_Printf("Device was lost. Restarting video\n");
|
||
Cmd_ExecuteString("vid_restart", RESTRICT_LOCAL);
|
||
return false;
|
||
}
|
||
|
||
if (vk.neednewswapchain && !vk.frame)
|
||
{
|
||
#ifdef MULTITHREAD
|
||
//kill the thread
|
||
if (vk.submitthread)
|
||
{
|
||
Sys_LockConditional(vk.submitcondition); //annoying, but required for it to be reliable with respect to other things.
|
||
Sys_ConditionSignal(vk.submitcondition);
|
||
Sys_UnlockConditional(vk.submitcondition);
|
||
Sys_WaitOnThread(vk.submitthread);
|
||
vk.submitthread = NULL;
|
||
}
|
||
#endif
|
||
//make sure any work is actually done BEFORE the swapchain gets destroyed
|
||
while (vk.work)
|
||
{
|
||
Sys_LockConditional(vk.submitcondition);
|
||
VK_Submit_DoWork();
|
||
Sys_UnlockConditional(vk.submitcondition);
|
||
}
|
||
if (vk.dopresent)
|
||
vk.dopresent(NULL);
|
||
vkDeviceWaitIdle(vk.device);
|
||
if (!VK_CreateSwapChain())
|
||
return false;
|
||
vk.neednewswapchain = false;
|
||
|
||
#ifdef MULTITHREAD
|
||
if (vk.allowsubmissionthread && (vk_submissionthread.ival || !*vk_submissionthread.string))
|
||
{
|
||
vk.submitthread = Sys_CreateThread("vksubmission", VK_Submit_Thread, NULL, THREADP_HIGHEST, 0);
|
||
}
|
||
#endif
|
||
}
|
||
|
||
if (!VK_SCR_GrabBackBuffer())
|
||
return false;
|
||
|
||
VKBE_Set2D(true);
|
||
VKBE_SelectDLight(NULL, vec3_origin, NULL, 0);
|
||
|
||
VK_PaintScreen();
|
||
|
||
if (R2D_Flush)
|
||
R2D_Flush();
|
||
|
||
vkCmdEndRenderPass(vk.rendertarg->cbuf);
|
||
|
||
fblayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
|
||
/*if (0)
|
||
{
|
||
vkscreencapture_t *capt = VK_AtFrameEnd(atframeend, sizeof(vkscreencapture_t));
|
||
VkImageMemoryBarrier imgbarrier = {VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER};
|
||
VkBufferImageCopy region;
|
||
imgbarrier.pNext = NULL;
|
||
imgbarrier.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
|
||
imgbarrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT;
|
||
imgbarrier.oldLayout = fblayout;
|
||
imgbarrier.newLayout = fblayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
|
||
imgbarrier.image = vk.frame->backbuf->colour.image;
|
||
imgbarrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||
imgbarrier.subresourceRange.baseMipLevel = 0;
|
||
imgbarrier.subresourceRange.levelCount = 1;
|
||
imgbarrier.subresourceRange.baseArrayLayer = 0;
|
||
imgbarrier.subresourceRange.layerCount = 1;
|
||
imgbarrier.srcQueueFamilyIndex = vk.queuefam[0];
|
||
imgbarrier.dstQueueFamilyIndex = vk.queuefam[0];
|
||
vkCmdPipelineBarrier(vk.frame->cbuf, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, 0, 0, NULL, 0, NULL, 1, &imgbarrier);
|
||
|
||
region.bufferOffset = 0;
|
||
region.bufferRowLength = 0; //tightly packed
|
||
region.bufferImageHeight = 0; //tightly packed
|
||
region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||
region.imageSubresource.mipLevel = 0;
|
||
region.imageSubresource.baseArrayLayer = 0;
|
||
region.imageSubresource.layerCount = 1;
|
||
region.imageOffset.x = 0;
|
||
region.imageOffset.y = 0;
|
||
region.imageOffset.z = 0;
|
||
region.imageExtent.width = capt->imagewidth = vk.frame->backbuf->colour.width;
|
||
region.imageExtent.height = capt->imageheight = vk.frame->backbuf->colour.height;
|
||
region.imageExtent.depth = 1;
|
||
vkCmdCopyImageToBuffer(vk.frame->cbuf, vk.frame->backbuf->colour.image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, buffer, 1, ®ion);
|
||
}*/
|
||
|
||
if (!(vk.frame->backbuf->rpassflags & RP_PRESENTABLE))
|
||
{
|
||
VkImageMemoryBarrier imgbarrier = {VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER};
|
||
imgbarrier.pNext = NULL;
|
||
imgbarrier.srcAccessMask = /*VK_ACCESS_TRANSFER_READ_BIT|*/VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
|
||
imgbarrier.dstAccessMask = 0;
|
||
imgbarrier.oldLayout = fblayout;
|
||
imgbarrier.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
|
||
imgbarrier.image = vk.frame->backbuf->colour.image;
|
||
imgbarrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||
imgbarrier.subresourceRange.baseMipLevel = 0;
|
||
imgbarrier.subresourceRange.levelCount = 1;
|
||
imgbarrier.subresourceRange.baseArrayLayer = 0;
|
||
imgbarrier.subresourceRange.layerCount = 1;
|
||
imgbarrier.srcQueueFamilyIndex = vk.queuefam[0];
|
||
imgbarrier.dstQueueFamilyIndex = vk.queuefam[1];
|
||
vkCmdPipelineBarrier(vk.rendertarg->cbuf, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, 0, 0, NULL, 0, NULL, 1, &imgbarrier);
|
||
vk.rendertarg->colour.layout = imgbarrier.newLayout;
|
||
}
|
||
|
||
// vkCmdWriteTimestamp(vk.rendertarg->cbuf, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, querypool, vk.bufferidx*2+1);
|
||
vkEndCommandBuffer(vk.rendertarg->cbuf);
|
||
|
||
VKBE_FlushDynamicBuffers();
|
||
|
||
{
|
||
struct vk_presented *fw = Z_Malloc(sizeof(*fw));
|
||
fw->fw.Passed = VK_Presented;
|
||
fw->fw.fence = vk.frame->finishedfence;
|
||
fw->frame = vk.frame;
|
||
//hand over any post-frame jobs to the frame in question.
|
||
vk.frame->frameendjobs = vk.frameendjobs;
|
||
vk.frameendjobs = NULL;
|
||
|
||
VK_Submit_Work(vk.rendertarg->cbuf, vk.frame->acquiresemaphore, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, vk.frame->backbuf->presentsemaphore, vk.frame->finishedfence, vk.frame, &fw->fw);
|
||
}
|
||
|
||
//now would be a good time to do any compute work or lightmap updates...
|
||
|
||
vk.frame = NULL;
|
||
|
||
VK_FencedCheck();
|
||
|
||
VID_SwapBuffers();
|
||
|
||
#ifdef TEXTEDITOR
|
||
if (editormodal)
|
||
{ //FIXME
|
||
VK_SCR_GrabBackBuffer();
|
||
}
|
||
#endif
|
||
return true;
|
||
}
|
||
|
||
void VKBE_RenderToTextureUpdate2d(qboolean destchanged)
|
||
{
|
||
}
|
||
|
||
static void VK_DestroyRenderPasses(void)
|
||
{
|
||
int i;
|
||
for (i = 0; i < countof(vk.renderpass); i++)
|
||
{
|
||
if (vk.renderpass[i] != VK_NULL_HANDLE)
|
||
{
|
||
vkDestroyRenderPass(vk.device, vk.renderpass[i], vkallocationcb);
|
||
vk.renderpass[i] = VK_NULL_HANDLE;
|
||
}
|
||
}
|
||
}
|
||
VkRenderPass VK_GetRenderPass(int pass)
|
||
{
|
||
int numattachments;
|
||
static VkAttachmentReference color_reference;
|
||
static VkAttachmentReference depth_reference;
|
||
static VkAttachmentReference resolve_reference;
|
||
static VkAttachmentDescription attachments[3] = {{0}};
|
||
static VkSubpassDescription subpass = {0};
|
||
static VkRenderPassCreateInfo rp_info = {VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO};
|
||
|
||
//two render passes are compatible for piplines when they match exactly except for:
|
||
//initial and final layouts in attachment descriptions.
|
||
//load and store operations in attachment descriptions.
|
||
//image layouts in attachment references.
|
||
|
||
if (vk.multisamplebits == VK_SAMPLE_COUNT_1_BIT)
|
||
pass &= ~RP_MULTISAMPLE; //no difference
|
||
|
||
if (vk.renderpass[pass] != VK_NULL_HANDLE)
|
||
return vk.renderpass[pass]; //already built
|
||
|
||
numattachments = 0;
|
||
if ((pass&3)==RP_DEPTHONLY)
|
||
color_reference.attachment = ~(uint32_t)0; //no colour buffer...
|
||
else
|
||
color_reference.attachment = numattachments++;
|
||
depth_reference.attachment = numattachments++;
|
||
resolve_reference.attachment = ~(uint32_t)0;
|
||
if ((pass & RP_MULTISAMPLE) && color_reference.attachment != ~(uint32_t)0)
|
||
{ //if we're using multisample, then render to a third texture, with a resolve to the original colour texture.
|
||
resolve_reference.attachment = color_reference.attachment;
|
||
color_reference.attachment = numattachments++;
|
||
}
|
||
|
||
color_reference.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
|
||
depth_reference.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
|
||
resolve_reference.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
|
||
|
||
if (color_reference.attachment != ~(uint32_t)0)
|
||
{
|
||
if (pass&RP_FP16)
|
||
attachments[color_reference.attachment].format = VK_FORMAT_R16G16B16A16_SFLOAT;
|
||
else if (pass&RP_VR)
|
||
attachments[color_reference.attachment].format = vk.backbufformat; //FIXME
|
||
else
|
||
attachments[color_reference.attachment].format = vk.backbufformat;
|
||
attachments[color_reference.attachment].samples = (pass & RP_MULTISAMPLE)?vk.multisamplebits:VK_SAMPLE_COUNT_1_BIT;
|
||
// attachments[color_reference.attachment].loadOp = pass?VK_ATTACHMENT_LOAD_OP_LOAD:VK_ATTACHMENT_LOAD_OP_DONT_CARE;
|
||
attachments[color_reference.attachment].storeOp = VK_ATTACHMENT_STORE_OP_STORE;
|
||
attachments[color_reference.attachment].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
|
||
attachments[color_reference.attachment].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
|
||
attachments[color_reference.attachment].initialLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
|
||
attachments[color_reference.attachment].finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
|
||
}
|
||
|
||
if (depth_reference.attachment != ~(uint32_t)0)
|
||
{
|
||
attachments[depth_reference.attachment].format = vk.depthformat;
|
||
attachments[depth_reference.attachment].samples = (pass & RP_MULTISAMPLE)?vk.multisamplebits:VK_SAMPLE_COUNT_1_BIT;
|
||
// attachments[depth_reference.attachment].loadOp = pass?VK_ATTACHMENT_LOAD_OP_LOAD:VK_ATTACHMENT_LOAD_OP_CLEAR;
|
||
attachments[depth_reference.attachment].storeOp = VK_ATTACHMENT_STORE_OP_STORE;//VK_ATTACHMENT_STORE_OP_DONT_CARE;
|
||
attachments[depth_reference.attachment].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
|
||
attachments[depth_reference.attachment].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
|
||
attachments[depth_reference.attachment].initialLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
|
||
attachments[depth_reference.attachment].finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
|
||
}
|
||
|
||
if (resolve_reference.attachment != ~(uint32_t)0)
|
||
{
|
||
attachments[resolve_reference.attachment].format = vk.backbufformat;
|
||
attachments[resolve_reference.attachment].samples = VK_SAMPLE_COUNT_1_BIT;
|
||
attachments[resolve_reference.attachment].loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
|
||
attachments[resolve_reference.attachment].storeOp = VK_ATTACHMENT_STORE_OP_STORE;
|
||
attachments[resolve_reference.attachment].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
|
||
attachments[resolve_reference.attachment].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
|
||
attachments[resolve_reference.attachment].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||
attachments[resolve_reference.attachment].finalLayout = (pass&RP_PRESENTABLE)?VK_IMAGE_LAYOUT_PRESENT_SRC_KHR:VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
||
}
|
||
|
||
subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
|
||
subpass.flags = 0;
|
||
subpass.inputAttachmentCount = 0;
|
||
subpass.pInputAttachments = NULL;
|
||
subpass.colorAttachmentCount = 1;
|
||
subpass.pColorAttachments = &color_reference;
|
||
subpass.pResolveAttachments = (resolve_reference.attachment != ~(uint32_t)0)?&resolve_reference:NULL;
|
||
subpass.pDepthStencilAttachment = &depth_reference;
|
||
subpass.preserveAttachmentCount = 0;
|
||
subpass.pPreserveAttachments = NULL;
|
||
|
||
rp_info.attachmentCount = numattachments;
|
||
rp_info.pAttachments = attachments;
|
||
rp_info.subpassCount = 1;
|
||
rp_info.pSubpasses = &subpass;
|
||
rp_info.dependencyCount = 0;
|
||
rp_info.pDependencies = NULL;
|
||
|
||
switch(pass&3)
|
||
{
|
||
case RP_RESUME:
|
||
//nothing cleared, both are just re-loaded.
|
||
attachments[color_reference.attachment].loadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
|
||
attachments[depth_reference.attachment].loadOp = VK_ATTACHMENT_LOAD_OP_LOAD;
|
||
break;
|
||
case RP_DEPTHCLEAR:
|
||
//depth cleared, colour is whatever.
|
||
attachments[depth_reference.attachment].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||
attachments[color_reference.attachment].loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
|
||
attachments[depth_reference.attachment].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
|
||
break;
|
||
case RP_FULLCLEAR:
|
||
//both cleared
|
||
attachments[color_reference.attachment].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||
attachments[depth_reference.attachment].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||
attachments[color_reference.attachment].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
|
||
attachments[depth_reference.attachment].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
|
||
break;
|
||
case RP_DEPTHONLY:
|
||
attachments[depth_reference.attachment].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
||
// attachments[color_reference.attachment].loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
|
||
attachments[depth_reference.attachment].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
|
||
|
||
attachments[depth_reference.attachment].finalLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
||
}
|
||
|
||
VkAssert(vkCreateRenderPass(vk.device, &rp_info, vkallocationcb, &vk.renderpass[pass]));
|
||
DebugSetName(VK_OBJECT_TYPE_RENDER_PASS, (uint64_t)vk.renderpass[pass], va("RP%i", pass));
|
||
return vk.renderpass[pass];
|
||
}
|
||
|
||
void VK_DoPresent(struct vkframe *theframe)
|
||
{
|
||
VkResult err;
|
||
uint32_t framenum;
|
||
VkPresentInfoKHR presinfo = {VK_STRUCTURE_TYPE_PRESENT_INFO_KHR};
|
||
if (!theframe)
|
||
return; //used to ensure that the queue is flushed at shutdown
|
||
framenum = theframe->backbuf - vk.backbufs;
|
||
presinfo.waitSemaphoreCount = 1;
|
||
presinfo.pWaitSemaphores = &theframe->backbuf->presentsemaphore;
|
||
presinfo.swapchainCount = 1;
|
||
presinfo.pSwapchains = &vk.swapchain;
|
||
presinfo.pImageIndices = &framenum;
|
||
|
||
{
|
||
RSpeedMark();
|
||
err = vkQueuePresentKHR(vk.queue_present, &presinfo);
|
||
RSpeedEnd(RSPEED_PRESENT);
|
||
}
|
||
{
|
||
RSpeedMark();
|
||
if (err)
|
||
{
|
||
if (err == VK_SUBOPTIMAL_KHR)
|
||
Con_DPrintf("vkQueuePresentKHR: VK_SUBOPTIMAL_KHR\n");
|
||
else if (err == VK_ERROR_OUT_OF_DATE_KHR)
|
||
Con_DPrintf("vkQueuePresentKHR: VK_ERROR_OUT_OF_DATE_KHR\n");
|
||
else
|
||
Con_Printf("ERROR: vkQueuePresentKHR: %i\n", err);
|
||
vk.neednewswapchain = true;
|
||
}
|
||
else
|
||
{
|
||
int r = vk.acquirelast%ACQUIRELIMIT;
|
||
uint64_t timeout = (vk.acquirelast==vk.acquirenext)?UINT64_MAX:0; //
|
||
err = vkAcquireNextImageKHR(vk.device, vk.swapchain, timeout, vk.acquiresemaphores[r], vk.acquirefences[r], &vk.acquirebufferidx[r]);
|
||
switch(err)
|
||
{
|
||
case VK_SUBOPTIMAL_KHR: //success, but with a warning.
|
||
vk.neednewswapchain = true;
|
||
vk.acquirelast++;
|
||
break;
|
||
case VK_SUCCESS: //success
|
||
vk.acquirelast++;
|
||
break;
|
||
|
||
//we gave the presentation engine an image, but its refusing to give us one back.
|
||
//logically this means the implementation lied about its VkSurfaceCapabilitiesKHR::minImageCount
|
||
case VK_TIMEOUT: //'success', yet still no result
|
||
case VK_NOT_READY:
|
||
//no idea how to handle. let it slip?
|
||
if (vk.acquirelast == vk.acquirenext)
|
||
vk.neednewswapchain = true; //slipped too much
|
||
break;
|
||
|
||
case VK_ERROR_OUT_OF_DATE_KHR:
|
||
//unable to present, but we at least don't need to throw everything away.
|
||
vk.neednewswapchain = true;
|
||
break;
|
||
case VK_ERROR_DEVICE_LOST:
|
||
case VK_ERROR_OUT_OF_HOST_MEMORY:
|
||
case VK_ERROR_OUT_OF_DEVICE_MEMORY:
|
||
case VK_ERROR_SURFACE_LOST_KHR:
|
||
//something really bad happened.
|
||
Con_Printf("ERROR: vkAcquireNextImageKHR: %s\n", VK_VKErrorToString(err));
|
||
vk.neednewswapchain = true;
|
||
vk.devicelost = true;
|
||
break;
|
||
default:
|
||
//case VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT:
|
||
//we don't know why we're getting this. vendor problem.
|
||
Con_Printf("ERROR: vkAcquireNextImageKHR: undocumented/extended %s\n", VK_VKErrorToString(err));
|
||
vk.neednewswapchain = true;
|
||
vk.devicelost = true; //this might be an infinite loop... no idea how to handle it.
|
||
break;
|
||
}
|
||
}
|
||
RSpeedEnd(RSPEED_ACQUIRE);
|
||
}
|
||
}
|
||
|
||
static void VK_Submit_DoWork(void)
|
||
{
|
||
VkCommandBuffer cbuf[64];
|
||
VkSemaphore wsem[64];
|
||
VkPipelineStageFlags wsemstageflags[64];
|
||
VkSemaphore ssem[64];
|
||
|
||
VkQueue subqueue = NULL;
|
||
VkSubmitInfo subinfo[64];
|
||
unsigned int subcount = 0;
|
||
struct vkwork_s *work;
|
||
struct vkframe *present = NULL;
|
||
VkFence waitfence = VK_NULL_HANDLE;
|
||
VkResult err;
|
||
struct vk_fencework *fencedwork = NULL;
|
||
qboolean errored = false;
|
||
|
||
while(vk.work && !present && !waitfence && !fencedwork && subcount < countof(subinfo))
|
||
{
|
||
work = vk.work;
|
||
if (subcount && subqueue != work->queue)
|
||
break;
|
||
subinfo[subcount].sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
|
||
subinfo[subcount].pNext = NULL;
|
||
subinfo[subcount].waitSemaphoreCount = work->semwait?1:0;
|
||
subinfo[subcount].pWaitSemaphores = &wsem[subcount];
|
||
wsem[subcount] = work->semwait;
|
||
subinfo[subcount].pWaitDstStageMask = &wsemstageflags[subcount];
|
||
wsemstageflags[subcount] = work->semwaitstagemask;
|
||
subinfo[subcount].commandBufferCount = work->cmdbuf?1:0;
|
||
subinfo[subcount].pCommandBuffers = &cbuf[subcount];
|
||
cbuf[subcount] = work->cmdbuf;
|
||
subinfo[subcount].signalSemaphoreCount = work->semsignal?1:0;
|
||
subinfo[subcount].pSignalSemaphores = &ssem[subcount];
|
||
ssem[subcount] = work->semsignal;
|
||
waitfence = work->fencesignal;
|
||
fencedwork = work->fencedwork;
|
||
subqueue = work->queue;
|
||
|
||
subcount++;
|
||
|
||
present = work->present;
|
||
|
||
vk.work = work->next;
|
||
Z_Free(work);
|
||
}
|
||
|
||
Sys_UnlockConditional(vk.submitcondition); //don't block people giving us work while we're occupied
|
||
if (subcount || waitfence)
|
||
{
|
||
RSpeedMark();
|
||
err = vkQueueSubmit(subqueue, subcount, subinfo, waitfence);
|
||
if (err)
|
||
{
|
||
if (!vk.devicelost)
|
||
Con_Printf(CON_ERROR "ERROR: vkQueueSubmit: %s\n", VK_VKErrorToString(err));
|
||
errored = vk.neednewswapchain = true;
|
||
vk.devicelost |= (err==VK_ERROR_DEVICE_LOST);
|
||
}
|
||
RSpeedEnd(RSPEED_SUBMIT);
|
||
}
|
||
|
||
if (present && !errored)
|
||
{
|
||
vk.dopresent(present);
|
||
}
|
||
|
||
Sys_LockConditional(vk.submitcondition);
|
||
|
||
if (fencedwork)
|
||
{ //this is used for loading and cleaning up things after the gpu has consumed it.
|
||
if (vk.fencework_last)
|
||
{
|
||
vk.fencework_last->next = fencedwork;
|
||
vk.fencework_last = fencedwork;
|
||
}
|
||
else
|
||
vk.fencework_last = vk.fencework = fencedwork;
|
||
}
|
||
}
|
||
|
||
#ifdef MULTITHREAD
|
||
//oh look. a thread.
|
||
//nvidia's drivers seem to like doing a lot of blocking in queuesubmit and queuepresent(despite the whole QUEUE thing).
|
||
//so thread this work so the main thread doesn't have to block so much.
|
||
int VK_Submit_Thread(void *arg)
|
||
{
|
||
Sys_LockConditional(vk.submitcondition);
|
||
while(!vk.neednewswapchain)
|
||
{
|
||
if (!vk.work)
|
||
Sys_ConditionWait(vk.submitcondition);
|
||
|
||
VK_Submit_DoWork();
|
||
}
|
||
Sys_UnlockConditional(vk.submitcondition);
|
||
return true;
|
||
}
|
||
#endif
|
||
|
||
void VK_Submit_Work(VkCommandBuffer cmdbuf, VkSemaphore semwait, VkPipelineStageFlags semwaitstagemask, VkSemaphore semsignal, VkFence fencesignal, struct vkframe *presentframe, struct vk_fencework *fencedwork)
|
||
{
|
||
struct vkwork_s *work = Z_Malloc(sizeof(*work));
|
||
struct vkwork_s **link;
|
||
|
||
work->queue = vk.queue_render;
|
||
work->cmdbuf = cmdbuf;
|
||
work->semwait = semwait;
|
||
work->semwaitstagemask = semwaitstagemask;
|
||
work->semsignal = semsignal;
|
||
work->fencesignal = fencesignal;
|
||
work->present = presentframe;
|
||
work->fencedwork = fencedwork;
|
||
|
||
Sys_LockConditional(vk.submitcondition);
|
||
|
||
#ifdef MULTITHREAD
|
||
if (vk.neednewswapchain && vk.submitthread)
|
||
{ //if we're trying to kill the submission thread, don't post work to it - instead wait for it to die cleanly then do it ourselves.
|
||
Sys_ConditionSignal(vk.submitcondition);
|
||
Sys_UnlockConditional(vk.submitcondition);
|
||
Sys_WaitOnThread(vk.submitthread);
|
||
vk.submitthread = NULL;
|
||
Sys_LockConditional(vk.submitcondition); //annoying, but required for it to be reliable with respect to other things.
|
||
}
|
||
#endif
|
||
|
||
//add it on the end in a lazy way.
|
||
for (link = &vk.work; *link; link = &(*link)->next)
|
||
;
|
||
*link = work;
|
||
|
||
#ifdef MULTITHREAD
|
||
if (vk.submitthread)
|
||
Sys_ConditionSignal(vk.submitcondition);
|
||
else
|
||
#endif
|
||
VK_Submit_DoWork();
|
||
Sys_UnlockConditional(vk.submitcondition);
|
||
}
|
||
|
||
void VK_Submit_Sync(void)
|
||
{
|
||
Sys_LockConditional(vk.submitcondition);
|
||
//FIXME:
|
||
vkDeviceWaitIdle(vk.device); //just in case
|
||
Sys_UnlockConditional(vk.submitcondition);
|
||
}
|
||
|
||
void VK_CheckTextureFormats(void)
|
||
{
|
||
struct {
|
||
unsigned int pti;
|
||
VkFormat vulkan;
|
||
unsigned int needextra;
|
||
} texfmt[] =
|
||
{
|
||
{PTI_RGBA8, VK_FORMAT_R8G8B8A8_UNORM, VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT},
|
||
{PTI_RGBX8, VK_FORMAT_R8G8B8A8_UNORM, VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT},
|
||
{PTI_BGRA8, VK_FORMAT_B8G8R8A8_UNORM, VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT},
|
||
{PTI_BGRX8, VK_FORMAT_B8G8R8A8_UNORM, VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT},
|
||
|
||
{PTI_RGB8, VK_FORMAT_R8G8B8_UNORM, VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT},
|
||
{PTI_BGR8, VK_FORMAT_B8G8R8_UNORM, VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT},
|
||
|
||
{PTI_RGBA8_SRGB, VK_FORMAT_R8G8B8A8_SRGB, VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT|VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT},
|
||
{PTI_RGBX8_SRGB, VK_FORMAT_R8G8B8A8_SRGB, VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT|VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT},
|
||
{PTI_BGRA8_SRGB, VK_FORMAT_B8G8R8A8_SRGB, VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT|VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT},
|
||
{PTI_BGRX8_SRGB, VK_FORMAT_B8G8R8A8_SRGB, VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT|VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT},
|
||
|
||
{PTI_E5BGR9, VK_FORMAT_E5B9G9R9_UFLOAT_PACK32, VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT|VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT},
|
||
{PTI_B10G11R11F, VK_FORMAT_B10G11R11_UFLOAT_PACK32, VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT|VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT},
|
||
{PTI_A2BGR10, VK_FORMAT_A2B10G10R10_UNORM_PACK32, VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT|VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT},
|
||
{PTI_RGB565, VK_FORMAT_R5G6B5_UNORM_PACK16, VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT},
|
||
{PTI_RGBA4444, VK_FORMAT_R4G4B4A4_UNORM_PACK16, VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT},
|
||
#ifdef VK_EXT_4444_formats
|
||
{PTI_ARGB4444, VK_FORMAT_A4R4G4B4_UNORM_PACK16_EXT,VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT},
|
||
#endif
|
||
{PTI_RGBA5551, VK_FORMAT_R5G5B5A1_UNORM_PACK16, VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT},
|
||
{PTI_ARGB1555, VK_FORMAT_A1R5G5B5_UNORM_PACK16, VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT},
|
||
{PTI_RGBA16F, VK_FORMAT_R16G16B16A16_SFLOAT, VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT|VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT|VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT},
|
||
{PTI_RGBA32F, VK_FORMAT_R32G32B32A32_SFLOAT, VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT|VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BIT|VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT},
|
||
{PTI_RGB32F, VK_FORMAT_R32G32B32_SFLOAT, VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT},
|
||
{PTI_L8, VK_FORMAT_R8_UNORM, VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT},
|
||
{PTI_L8A8, VK_FORMAT_R8G8_UNORM, VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT},
|
||
{PTI_L8_SRGB, VK_FORMAT_R8_SRGB, VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT},
|
||
{PTI_R8, VK_FORMAT_R8_UNORM, VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT},
|
||
{PTI_RG8, VK_FORMAT_R8G8_UNORM, VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT},
|
||
{PTI_R8_SNORM, VK_FORMAT_R8_SNORM, VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT},
|
||
{PTI_RG8_SNORM, VK_FORMAT_R8G8_SNORM, VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT},
|
||
{PTI_R16, VK_FORMAT_R16_UNORM, VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT},
|
||
{PTI_RGBA16, VK_FORMAT_R16G16B16A16_UNORM, VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT},
|
||
{PTI_R16F, VK_FORMAT_R16_SFLOAT, VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT},
|
||
{PTI_R32F, VK_FORMAT_R32_SFLOAT, VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT},
|
||
|
||
{PTI_DEPTH16, VK_FORMAT_D16_UNORM, VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT},
|
||
{PTI_DEPTH24, VK_FORMAT_X8_D24_UNORM_PACK32, VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT},
|
||
{PTI_DEPTH32, VK_FORMAT_D32_SFLOAT, VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT},
|
||
{PTI_DEPTH24_8, VK_FORMAT_D24_UNORM_S8_UINT, VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT},
|
||
|
||
{PTI_BC1_RGB, VK_FORMAT_BC1_RGB_UNORM_BLOCK, VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT},
|
||
{PTI_BC1_RGBA, VK_FORMAT_BC1_RGBA_UNORM_BLOCK, VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT},
|
||
{PTI_BC2_RGBA, VK_FORMAT_BC2_UNORM_BLOCK, VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT},
|
||
{PTI_BC3_RGBA, VK_FORMAT_BC3_UNORM_BLOCK, VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT},
|
||
{PTI_BC1_RGB_SRGB, VK_FORMAT_BC1_RGB_SRGB_BLOCK, VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT},
|
||
{PTI_BC1_RGBA_SRGB, VK_FORMAT_BC1_RGBA_SRGB_BLOCK, VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT},
|
||
{PTI_BC2_RGBA_SRGB, VK_FORMAT_BC2_SRGB_BLOCK, VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT},
|
||
{PTI_BC3_RGBA_SRGB, VK_FORMAT_BC3_SRGB_BLOCK, VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT},
|
||
{PTI_BC4_R, VK_FORMAT_BC4_UNORM_BLOCK, VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT},
|
||
{PTI_BC4_R_SNORM, VK_FORMAT_BC4_SNORM_BLOCK, VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT},
|
||
{PTI_BC5_RG, VK_FORMAT_BC5_UNORM_BLOCK, VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT},
|
||
{PTI_BC5_RG_SNORM, VK_FORMAT_BC5_SNORM_BLOCK, VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT},
|
||
{PTI_BC6_RGB_UFLOAT, VK_FORMAT_BC6H_UFLOAT_BLOCK, VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT},
|
||
{PTI_BC6_RGB_SFLOAT, VK_FORMAT_BC6H_SFLOAT_BLOCK, VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT},
|
||
{PTI_BC7_RGBA, VK_FORMAT_BC7_UNORM_BLOCK, VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT},
|
||
{PTI_BC7_RGBA_SRGB, VK_FORMAT_BC7_SRGB_BLOCK, VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT},
|
||
{PTI_ETC1_RGB8, VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK, VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT}, //vulkan doesn't support etc1 (but that's okay, because etc2 is a superset).
|
||
{PTI_ETC2_RGB8, VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK, VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT},
|
||
{PTI_ETC2_RGB8A1, VK_FORMAT_ETC2_R8G8B8A1_UNORM_BLOCK,VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT},
|
||
{PTI_ETC2_RGB8A8, VK_FORMAT_ETC2_R8G8B8A8_UNORM_BLOCK,VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT},
|
||
{PTI_ETC2_RGB8_SRGB, VK_FORMAT_ETC2_R8G8B8_SRGB_BLOCK, VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT},
|
||
{PTI_ETC2_RGB8A1_SRGB, VK_FORMAT_ETC2_R8G8B8A1_SRGB_BLOCK, VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT},
|
||
{PTI_ETC2_RGB8A8_SRGB, VK_FORMAT_ETC2_R8G8B8A8_SRGB_BLOCK, VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT},
|
||
{PTI_EAC_R11, VK_FORMAT_EAC_R11_UNORM_BLOCK, VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT},
|
||
{PTI_EAC_R11_SNORM, VK_FORMAT_EAC_R11_SNORM_BLOCK, VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT},
|
||
{PTI_EAC_RG11, VK_FORMAT_EAC_R11G11_UNORM_BLOCK, VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT},
|
||
{PTI_EAC_RG11_SNORM, VK_FORMAT_EAC_R11G11_SNORM_BLOCK, VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT},
|
||
{PTI_ASTC_4X4_LDR, VK_FORMAT_ASTC_4x4_UNORM_BLOCK, VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT},
|
||
{PTI_ASTC_5X4_LDR, VK_FORMAT_ASTC_5x4_UNORM_BLOCK, VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT},
|
||
{PTI_ASTC_5X5_LDR, VK_FORMAT_ASTC_5x5_UNORM_BLOCK, VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT},
|
||
{PTI_ASTC_6X5_LDR, VK_FORMAT_ASTC_6x5_UNORM_BLOCK, VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT},
|
||
{PTI_ASTC_6X6_LDR, VK_FORMAT_ASTC_6x6_UNORM_BLOCK, VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT},
|
||
{PTI_ASTC_8X5_LDR, VK_FORMAT_ASTC_8x5_UNORM_BLOCK, VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT},
|
||
{PTI_ASTC_8X6_LDR, VK_FORMAT_ASTC_8x6_UNORM_BLOCK, VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT},
|
||
{PTI_ASTC_8X8_LDR, VK_FORMAT_ASTC_8x8_UNORM_BLOCK, VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT},
|
||
{PTI_ASTC_10X5_LDR, VK_FORMAT_ASTC_10x5_UNORM_BLOCK, VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT},
|
||
{PTI_ASTC_10X6_LDR, VK_FORMAT_ASTC_10x6_UNORM_BLOCK, VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT},
|
||
{PTI_ASTC_10X8_LDR, VK_FORMAT_ASTC_10x8_UNORM_BLOCK, VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT},
|
||
{PTI_ASTC_10X10_LDR, VK_FORMAT_ASTC_10x10_UNORM_BLOCK, VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT},
|
||
{PTI_ASTC_12X10_LDR, VK_FORMAT_ASTC_12x10_UNORM_BLOCK, VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT},
|
||
{PTI_ASTC_12X12_LDR, VK_FORMAT_ASTC_12x12_UNORM_BLOCK, VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT},
|
||
{PTI_ASTC_4X4_SRGB, VK_FORMAT_ASTC_4x4_SRGB_BLOCK, VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT},
|
||
{PTI_ASTC_5X4_SRGB, VK_FORMAT_ASTC_5x4_SRGB_BLOCK, VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT},
|
||
{PTI_ASTC_5X5_SRGB, VK_FORMAT_ASTC_5x5_SRGB_BLOCK, VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT},
|
||
{PTI_ASTC_6X5_SRGB, VK_FORMAT_ASTC_6x5_SRGB_BLOCK, VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT},
|
||
{PTI_ASTC_6X6_SRGB, VK_FORMAT_ASTC_6x6_SRGB_BLOCK, VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT},
|
||
{PTI_ASTC_8X5_SRGB, VK_FORMAT_ASTC_8x5_SRGB_BLOCK, VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT},
|
||
{PTI_ASTC_8X6_SRGB, VK_FORMAT_ASTC_8x6_SRGB_BLOCK, VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT},
|
||
{PTI_ASTC_8X8_SRGB, VK_FORMAT_ASTC_8x8_SRGB_BLOCK, VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT},
|
||
{PTI_ASTC_10X5_SRGB, VK_FORMAT_ASTC_10x5_SRGB_BLOCK, VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT},
|
||
{PTI_ASTC_10X6_SRGB, VK_FORMAT_ASTC_10x6_SRGB_BLOCK, VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT},
|
||
{PTI_ASTC_10X8_SRGB, VK_FORMAT_ASTC_10x8_SRGB_BLOCK, VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT},
|
||
{PTI_ASTC_10X10_SRGB, VK_FORMAT_ASTC_10x10_SRGB_BLOCK, VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT},
|
||
{PTI_ASTC_12X10_SRGB, VK_FORMAT_ASTC_12x10_SRGB_BLOCK, VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT},
|
||
{PTI_ASTC_12X12_SRGB, VK_FORMAT_ASTC_12x12_SRGB_BLOCK, VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT},
|
||
|
||
#ifdef VK_EXT_texture_compression_astc_hdr
|
||
{PTI_ASTC_4X4_HDR, VK_FORMAT_ASTC_4x4_SFLOAT_BLOCK_EXT, VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT},
|
||
{PTI_ASTC_5X4_HDR, VK_FORMAT_ASTC_5x4_SFLOAT_BLOCK_EXT, VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT},
|
||
{PTI_ASTC_5X5_HDR, VK_FORMAT_ASTC_5x5_SFLOAT_BLOCK_EXT, VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT},
|
||
{PTI_ASTC_6X5_HDR, VK_FORMAT_ASTC_6x5_SFLOAT_BLOCK_EXT, VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT},
|
||
{PTI_ASTC_6X6_HDR, VK_FORMAT_ASTC_6x6_SFLOAT_BLOCK_EXT, VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT},
|
||
{PTI_ASTC_8X5_HDR, VK_FORMAT_ASTC_8x5_SFLOAT_BLOCK_EXT, VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT},
|
||
{PTI_ASTC_8X6_HDR, VK_FORMAT_ASTC_8x6_SFLOAT_BLOCK_EXT, VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT},
|
||
{PTI_ASTC_8X8_HDR, VK_FORMAT_ASTC_8x8_SFLOAT_BLOCK_EXT, VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT},
|
||
{PTI_ASTC_10X5_HDR, VK_FORMAT_ASTC_10x5_SFLOAT_BLOCK_EXT, VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT},
|
||
{PTI_ASTC_10X6_HDR, VK_FORMAT_ASTC_10x6_SFLOAT_BLOCK_EXT, VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT},
|
||
{PTI_ASTC_10X8_HDR, VK_FORMAT_ASTC_10x8_SFLOAT_BLOCK_EXT, VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT},
|
||
{PTI_ASTC_10X10_HDR, VK_FORMAT_ASTC_10x10_SFLOAT_BLOCK_EXT, VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT},
|
||
{PTI_ASTC_12X10_HDR, VK_FORMAT_ASTC_12x10_SFLOAT_BLOCK_EXT, VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT},
|
||
{PTI_ASTC_12X12_HDR, VK_FORMAT_ASTC_12x12_SFLOAT_BLOCK_EXT, VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT},
|
||
#endif
|
||
};
|
||
unsigned int i;
|
||
VkPhysicalDeviceProperties props;
|
||
|
||
vkGetPhysicalDeviceProperties(vk.gpu, &props);
|
||
vk.limits = props.limits;
|
||
|
||
sh_config.texture2d_maxsize = props.limits.maxImageDimension2D;
|
||
sh_config.texturecube_maxsize = props.limits.maxImageDimensionCube;
|
||
sh_config.texture2darray_maxlayers = props.limits.maxImageArrayLayers;
|
||
|
||
for (i = 0; i < countof(texfmt); i++)
|
||
{
|
||
unsigned int need = /*VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT |*/ texfmt[i].needextra;
|
||
VkFormatProperties fmt;
|
||
vkGetPhysicalDeviceFormatProperties(vk.gpu, texfmt[i].vulkan, &fmt);
|
||
|
||
if ((fmt.optimalTilingFeatures & need) == need)
|
||
sh_config.texfmt[texfmt[i].pti] = true;
|
||
}
|
||
|
||
if (sh_config.texfmt[PTI_BC1_RGBA] && sh_config.texfmt[PTI_BC2_RGBA] && sh_config.texfmt[PTI_BC3_RGBA] && sh_config.texfmt[PTI_BC5_RG] && sh_config.texfmt[PTI_BC7_RGBA])
|
||
sh_config.hw_bc = 3;
|
||
if (sh_config.texfmt[PTI_ETC2_RGB8] && sh_config.texfmt[PTI_ETC2_RGB8A1] && sh_config.texfmt[PTI_ETC2_RGB8A8] && sh_config.texfmt[PTI_EAC_RG11])
|
||
sh_config.hw_etc = 2;
|
||
if (sh_config.texfmt[PTI_ASTC_4X4_LDR])
|
||
sh_config.hw_astc = 1; //the core vulkan formats refer to the ldr profile. hdr is a separate extension, which is still not properly specified..
|
||
if (sh_config.texfmt[PTI_ASTC_4X4_HDR])
|
||
sh_config.hw_astc = 2; //the core vulkan formats refer to the ldr profile. hdr is a separate extension, which is still not properly specified..
|
||
}
|
||
|
||
//creates a vulkan instance with the additional extensions, and hands a copy of the instance to the caller.
|
||
qboolean VK_CreateInstance(vrsetup_t *info, char *vrexts, void *result)
|
||
{
|
||
VkInstanceCreateInfo inst_info = *(VkInstanceCreateInfo*)info->userctx;
|
||
VkResult err;
|
||
const char *ext[64];
|
||
unsigned int numext = inst_info.enabledExtensionCount;
|
||
memcpy(ext, inst_info.ppEnabledExtensionNames, numext*sizeof(*ext));
|
||
while (vrexts && numext < countof(ext))
|
||
{
|
||
ext[numext++] = vrexts;
|
||
vrexts = strchr(vrexts, ' ');
|
||
if (!vrexts)
|
||
break;
|
||
*vrexts++ = 0;
|
||
}
|
||
|
||
err = vkCreateInstance(&inst_info, vkallocationcb, &vk.instance);
|
||
switch(err)
|
||
{
|
||
case VK_ERROR_INCOMPATIBLE_DRIVER:
|
||
Con_Printf("VK_ERROR_INCOMPATIBLE_DRIVER: please install an appropriate vulkan driver\n");
|
||
return false;
|
||
case VK_ERROR_EXTENSION_NOT_PRESENT:
|
||
Con_Printf("VK_ERROR_EXTENSION_NOT_PRESENT: something on a system level is probably misconfigured\n");
|
||
return false;
|
||
case VK_ERROR_LAYER_NOT_PRESENT:
|
||
Con_Printf("VK_ERROR_LAYER_NOT_PRESENT: requested layer is not known/usable\n");
|
||
return false;
|
||
default:
|
||
Con_Printf("Unknown vulkan instance creation error: %x\n", err);
|
||
return false;
|
||
case VK_SUCCESS:
|
||
break;
|
||
}
|
||
|
||
if (result)
|
||
*(VkInstance*)result = vk.instance;
|
||
return true;
|
||
}
|
||
|
||
qboolean VK_EnumerateDevices (void *usercontext, void(*callback)(void *context, const char *devicename, const char *outputname, const char *desc), const char *descprefix, PFN_vkGetInstanceProcAddr vk_GetInstanceProcAddr)
|
||
{
|
||
VkInstance vk_instance;
|
||
|
||
VkApplicationInfo app;
|
||
VkInstanceCreateInfo inst_info;
|
||
|
||
#if 0 //for quicky debugging...
|
||
#define VKFunc(n) int vk##n;
|
||
VKFuncs
|
||
#undef VKFunc
|
||
#endif
|
||
|
||
#define VKFunc(n) PFN_vk##n vk_##n;
|
||
VKFunc(CreateInstance)
|
||
|
||
VKFunc(DestroyInstance)
|
||
VKFunc(EnumeratePhysicalDevices)
|
||
VKFunc(GetPhysicalDeviceProperties)
|
||
#undef VKFunc
|
||
|
||
//get second set of pointers... (instance-level)
|
||
#ifdef VK_NO_PROTOTYPES
|
||
if (!vk_GetInstanceProcAddr)
|
||
return false;
|
||
#define VKFunc(n) vk_##n = (PFN_vk##n)vk_GetInstanceProcAddr(VK_NULL_HANDLE, "vk"#n);
|
||
//VKFunc(EnumerateInstanceLayerProperties)
|
||
//VKFunc(EnumerateInstanceExtensionProperties)
|
||
VKFunc(CreateInstance)
|
||
#undef VKFunc
|
||
#endif
|
||
|
||
memset(&app, 0, sizeof(app));
|
||
app.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
|
||
app.pNext = NULL;
|
||
app.pApplicationName = FULLENGINENAME;
|
||
app.applicationVersion = revision_number(false);
|
||
app.pEngineName = "FTE Quake";
|
||
app.engineVersion = VK_MAKE_VERSION(FTE_VER_MAJOR, FTE_VER_MINOR, 0);
|
||
app.apiVersion = VK_API_MAX_VERSION;
|
||
|
||
memset(&inst_info, 0, sizeof(inst_info));
|
||
inst_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
|
||
inst_info.pApplicationInfo = &app;
|
||
inst_info.enabledLayerCount = vklayercount;
|
||
inst_info.ppEnabledLayerNames = vklayerlist;
|
||
inst_info.enabledExtensionCount = 0;
|
||
inst_info.ppEnabledExtensionNames = NULL;
|
||
|
||
if (vk_CreateInstance(&inst_info, vkallocationcb, &vk_instance) != VK_SUCCESS)
|
||
return false;
|
||
|
||
//third set of functions...
|
||
#ifdef VK_NO_PROTOTYPES
|
||
//vk_GetInstanceProcAddr = (PFN_vkGetInstanceProcAddr)vk_GetInstanceProcAddr(vk_instance, "vkGetInstanceProcAddr");
|
||
#define VKFunc(n) vk_##n = (PFN_vk##n)vk_GetInstanceProcAddr(vk_instance, "vk"#n);
|
||
VKFunc(DestroyInstance)
|
||
VKFunc(EnumeratePhysicalDevices)
|
||
VKFunc(GetPhysicalDeviceProperties)
|
||
#undef VKFunc
|
||
#endif
|
||
|
||
//enumerate the gpus
|
||
{
|
||
uint32_t gpucount = 0, i;
|
||
VkPhysicalDevice *devs;
|
||
char gpuname[64];
|
||
|
||
vk_EnumeratePhysicalDevices(vk_instance, &gpucount, NULL);
|
||
if (!gpucount)
|
||
return false;
|
||
devs = malloc(sizeof(VkPhysicalDevice)*gpucount);
|
||
vk_EnumeratePhysicalDevices(vk_instance, &gpucount, devs);
|
||
for (i = 0; i < gpucount; i++)
|
||
{
|
||
VkPhysicalDeviceProperties props;
|
||
vk_GetPhysicalDeviceProperties(devs[i], &props);
|
||
|
||
Q_snprintfz(gpuname, sizeof(gpuname), "GPU%u", i);
|
||
//FIXME: make sure its not a number or GPU#...
|
||
callback(usercontext, gpuname, "", va("%s%s", descprefix, props.deviceName));
|
||
}
|
||
free(devs);
|
||
}
|
||
|
||
vk_DestroyInstance(vk_instance, vkallocationcb);
|
||
return true;
|
||
}
|
||
|
||
//initialise the vulkan instance, context, device, etc.
|
||
qboolean VK_Init(rendererstate_t *info, const char **sysextnames, qboolean (*createSurface)(void), void (*dopresent)(struct vkframe *theframe))
|
||
{
|
||
VkQueueFamilyProperties *queueprops;
|
||
VkResult err;
|
||
VkApplicationInfo app;
|
||
VkInstanceCreateInfo inst_info;
|
||
int gpuidx = 0;
|
||
const char *extensions[8];
|
||
uint32_t extensions_count = 0;
|
||
|
||
qboolean ignorequeuebugs = false;
|
||
qboolean okay;
|
||
vrsetup_t vrsetup = {sizeof(vrsetup)};
|
||
|
||
//device extensions that want to enable
|
||
//initialised in reverse order, so superseeded should name later extensions.
|
||
struct
|
||
{
|
||
qboolean *flag;
|
||
const char *name;
|
||
cvar_t *var;
|
||
qboolean def;
|
||
qboolean *superseeded; //if this is set then the extension will not be enabled after all
|
||
const char *warningtext; //printed if the extension is requested but not supported by the device
|
||
qboolean supported;
|
||
} knowndevexts[] =
|
||
{
|
||
{&vk.khr_swapchain, VK_KHR_SWAPCHAIN_EXTENSION_NAME, NULL, true, NULL, " Nothing will be drawn!"},
|
||
{&vk.khr_get_memory_requirements2, VK_KHR_GET_MEMORY_REQUIREMENTS_2_EXTENSION_NAME,&vk_khr_get_memory_requirements2,true, NULL, NULL},
|
||
{&vk.khr_dedicated_allocation, VK_KHR_DEDICATED_ALLOCATION_EXTENSION_NAME, &vk_khr_dedicated_allocation, true, NULL, NULL},
|
||
{&vk.khr_push_descriptor, VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME, &vk_khr_push_descriptor, true, NULL, NULL},
|
||
{&vk.amd_rasterization_order, VK_AMD_RASTERIZATION_ORDER_EXTENSION_NAME, &vk_amd_rasterization_order, false, NULL, NULL},
|
||
#ifdef VK_EXT_astc_decode_mode
|
||
{&vk.ext_astc_decode_mode, VK_EXT_ASTC_DECODE_MODE_EXTENSION_NAME, &vk_ext_astc_decode_mode, true, NULL, NULL},
|
||
#endif
|
||
};
|
||
size_t e;
|
||
|
||
for (e = 0; e < countof(knowndevexts); e++)
|
||
*knowndevexts[e].flag = false;
|
||
vk.neednewswapchain = true;
|
||
vk.triplebuffer = info->triplebuffer;
|
||
vk.vsync = info->wait;
|
||
vk.dopresent = dopresent?dopresent:VK_DoPresent;
|
||
memset(&sh_config, 0, sizeof(sh_config));
|
||
|
||
|
||
//get second set of pointers... (instance-level)
|
||
#ifdef VK_NO_PROTOTYPES
|
||
if (!vkGetInstanceProcAddr)
|
||
{
|
||
Con_Printf(CON_ERROR"vkGetInstanceProcAddr is null\n");
|
||
return false;
|
||
}
|
||
#define VKFunc(n) vk##n = (PFN_vk##n)vkGetInstanceProcAddr(VK_NULL_HANDLE, "vk"#n);
|
||
VKInstFuncs
|
||
#undef VKFunc
|
||
#endif
|
||
|
||
//try and enable some instance extensions...
|
||
{
|
||
qboolean surfext = false;
|
||
uint32_t count, i, j;
|
||
VkExtensionProperties *ext;
|
||
#ifdef VK_EXT_debug_utils
|
||
qboolean havedebugutils = false;
|
||
#endif
|
||
#ifdef VK_EXT_debug_report
|
||
qboolean havedebugreport = false;
|
||
#endif
|
||
if (VK_SUCCESS!=vkEnumerateInstanceExtensionProperties(NULL, &count, NULL))
|
||
count = 0;
|
||
ext = malloc(sizeof(*ext)*count);
|
||
if (!ext || VK_SUCCESS!=vkEnumerateInstanceExtensionProperties(NULL, &count, ext))
|
||
count = 0;
|
||
for (i = 0; i < count && extensions_count < countof(extensions); i++)
|
||
{
|
||
Con_DLPrintf(2, " vki: %s\n", ext[i].extensionName);
|
||
#ifdef VK_EXT_debug_utils
|
||
if (!strcmp(ext[i].extensionName, VK_EXT_DEBUG_REPORT_EXTENSION_NAME))
|
||
havedebugutils = true;
|
||
#endif
|
||
#ifdef VK_EXT_debug_report
|
||
if (!strcmp(ext[i].extensionName, VK_EXT_DEBUG_REPORT_EXTENSION_NAME))
|
||
havedebugreport = true;
|
||
#endif
|
||
if (!strcmp(ext[i].extensionName, VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME))
|
||
extensions[extensions_count++] = VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME;
|
||
else if (sysextnames && !strcmp(ext[i].extensionName, VK_KHR_SURFACE_EXTENSION_NAME))
|
||
{
|
||
extensions[extensions_count++] = VK_KHR_SURFACE_EXTENSION_NAME;
|
||
surfext = true;
|
||
}
|
||
else if (sysextnames)
|
||
{
|
||
for (j = 0; sysextnames[j]; j++)
|
||
{
|
||
if (!strcmp(ext[i].extensionName, sysextnames[j]))
|
||
{
|
||
extensions[extensions_count++] = sysextnames[j];
|
||
vk.khr_swapchain = true;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
free(ext);
|
||
|
||
if (!vk_debug.ival)
|
||
;
|
||
#ifdef VK_EXT_debug_utils
|
||
else if (havedebugutils)
|
||
extensions[extensions_count++] = VK_EXT_DEBUG_UTILS_EXTENSION_NAME;
|
||
#endif
|
||
#ifdef VK_EXT_debug_report
|
||
else if (havedebugreport)
|
||
extensions[extensions_count++] = VK_EXT_DEBUG_REPORT_EXTENSION_NAME;
|
||
#endif
|
||
|
||
if (sysextnames && (!vk.khr_swapchain || !surfext))
|
||
{
|
||
Con_TPrintf(CON_ERROR"Vulkan instance lacks driver support for %s\n", sysextnames[0]);
|
||
return false;
|
||
}
|
||
}
|
||
|
||
#define ENGINEVERSION 1
|
||
memset(&app, 0, sizeof(app));
|
||
app.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
|
||
app.pNext = NULL;
|
||
app.pApplicationName = FULLENGINENAME;
|
||
app.applicationVersion = revision_number(false);
|
||
app.pEngineName = "FTE Quake";
|
||
app.engineVersion = VK_MAKE_VERSION(FTE_VER_MAJOR, FTE_VER_MINOR, 0);
|
||
app.apiVersion = VK_API_MAX_VERSION;
|
||
|
||
memset(&inst_info, 0, sizeof(inst_info));
|
||
inst_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
|
||
inst_info.pApplicationInfo = &app;
|
||
inst_info.enabledLayerCount = vklayercount;
|
||
inst_info.ppEnabledLayerNames = vklayerlist;
|
||
inst_info.enabledExtensionCount = extensions_count;
|
||
inst_info.ppEnabledExtensionNames = extensions;
|
||
|
||
vrsetup.vrplatform = VR_VULKAN;
|
||
vrsetup.userctx = &inst_info;
|
||
vrsetup.createinstance = VK_CreateInstance;
|
||
if (info->vr)
|
||
{
|
||
okay = info->vr->Prepare(&vrsetup);
|
||
if (!okay)
|
||
{
|
||
info->vr->Shutdown();
|
||
info->vr = NULL;
|
||
}
|
||
}
|
||
else
|
||
okay = false;
|
||
if (!okay)
|
||
okay = vrsetup.createinstance(&vrsetup, NULL, NULL);
|
||
if (!okay)
|
||
{
|
||
if (info->vr)
|
||
info->vr->Shutdown();
|
||
return false;
|
||
}
|
||
vid.vr = info->vr;
|
||
|
||
#ifdef MULTITHREAD
|
||
vk.allowsubmissionthread = !vid.vr;
|
||
#endif
|
||
|
||
//third set of functions...
|
||
#ifdef VK_NO_PROTOTYPES
|
||
//vkGetInstanceProcAddr = (PFN_vkGetInstanceProcAddr)vkGetInstanceProcAddr(vk.instance, "vkGetInstanceProcAddr");
|
||
#define VKFunc(n) vk##n = (PFN_vk##n)vkGetInstanceProcAddr(vk.instance, "vk"#n);
|
||
VKInst2Funcs
|
||
#undef VKFunc
|
||
#endif
|
||
|
||
//set up debug callbacks
|
||
if (vk_debug.ival)
|
||
{
|
||
#ifdef VK_EXT_debug_utils
|
||
vkCreateDebugUtilsMessengerEXT = (PFN_vkCreateDebugUtilsMessengerEXT)vkGetInstanceProcAddr(vk.instance, "vkCreateDebugUtilsMessengerEXT");
|
||
vkDestroyDebugUtilsMessengerEXT = (PFN_vkDestroyDebugUtilsMessengerEXT)vkGetInstanceProcAddr(vk.instance, "vkDestroyDebugUtilsMessengerEXT");
|
||
if (vkCreateDebugUtilsMessengerEXT)
|
||
{
|
||
VkDebugUtilsMessengerCreateInfoEXT dbgCreateInfo;
|
||
memset(&dbgCreateInfo, 0, sizeof(dbgCreateInfo));
|
||
dbgCreateInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT;
|
||
dbgCreateInfo.pfnUserCallback = mydebugutilsmessagecallback;
|
||
dbgCreateInfo.pUserData = NULL;
|
||
dbgCreateInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT |
|
||
VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT |
|
||
VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT |
|
||
VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT;
|
||
dbgCreateInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT |
|
||
VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT |
|
||
VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT;
|
||
vkCreateDebugUtilsMessengerEXT(vk.instance, &dbgCreateInfo, vkallocationcb, &vk_debugucallback);
|
||
}
|
||
#endif
|
||
#ifdef VK_EXT_debug_report
|
||
vkCreateDebugReportCallbackEXT = (PFN_vkCreateDebugReportCallbackEXT)vkGetInstanceProcAddr(vk.instance, "vkCreateDebugReportCallbackEXT");
|
||
vkDestroyDebugReportCallbackEXT = (PFN_vkDestroyDebugReportCallbackEXT)vkGetInstanceProcAddr(vk.instance, "vkDestroyDebugReportCallbackEXT");
|
||
if (vkCreateDebugReportCallbackEXT && vkDestroyDebugReportCallbackEXT)
|
||
{
|
||
VkDebugReportCallbackCreateInfoEXT dbgCreateInfo;
|
||
memset(&dbgCreateInfo, 0, sizeof(dbgCreateInfo));
|
||
dbgCreateInfo.sType = VK_STRUCTURE_TYPE_DEBUG_REPORT_CREATE_INFO_EXT;
|
||
dbgCreateInfo.pfnCallback = mydebugreportcallback;
|
||
dbgCreateInfo.pUserData = NULL;
|
||
dbgCreateInfo.flags = VK_DEBUG_REPORT_ERROR_BIT_EXT |
|
||
VK_DEBUG_REPORT_WARNING_BIT_EXT |
|
||
/* VK_DEBUG_REPORT_INFORMATION_BIT_EXT | */
|
||
VK_DEBUG_REPORT_PERFORMANCE_WARNING_BIT_EXT |
|
||
VK_DEBUG_REPORT_DEBUG_BIT_EXT;
|
||
vkCreateDebugReportCallbackEXT(vk.instance, &dbgCreateInfo, vkallocationcb, &vk_debugcallback);
|
||
}
|
||
#endif
|
||
}
|
||
|
||
//create the platform-specific surface
|
||
createSurface();
|
||
|
||
//figure out which gpu we're going to use
|
||
{
|
||
uint32_t gpucount = 0, i;
|
||
uint32_t bestpri = ~0u, pri;
|
||
VkPhysicalDevice *devs;
|
||
char *s = info->subrenderer;
|
||
int wantdev = -1;
|
||
if (*s)
|
||
{
|
||
if (!Q_strncasecmp(s, "GPU", 3))
|
||
s += 3;
|
||
wantdev = strtoul(s, &s, 0);
|
||
if (*s) //its a named device.
|
||
wantdev = -1;
|
||
}
|
||
|
||
vkEnumeratePhysicalDevices(vk.instance, &gpucount, NULL);
|
||
if (!gpucount)
|
||
{
|
||
Con_Printf(CON_ERROR"vulkan: no devices known!\n");
|
||
return false;
|
||
}
|
||
devs = malloc(sizeof(VkPhysicalDevice)*gpucount);
|
||
vkEnumeratePhysicalDevices(vk.instance, &gpucount, devs);
|
||
for (i = 0; i < gpucount; i++)
|
||
{
|
||
VkPhysicalDeviceProperties props;
|
||
uint32_t j, queue_count = 0;
|
||
vkGetPhysicalDeviceProperties(devs[i], &props);
|
||
vkGetPhysicalDeviceQueueFamilyProperties(devs[i], &queue_count, NULL);
|
||
|
||
if (vk.khr_swapchain)
|
||
{
|
||
for (j = 0; j < queue_count; j++)
|
||
{
|
||
VkBool32 supportsPresent = false;
|
||
VkAssert(vkGetPhysicalDeviceSurfaceSupportKHR(devs[i], j, vk.surface, &supportsPresent));
|
||
if (supportsPresent)
|
||
break; //okay, this one should be usable
|
||
}
|
||
if (j == queue_count)
|
||
{
|
||
if ((wantdev >= 0 && i==wantdev) || (wantdev==-1 && *info->subrenderer && !Q_strcasecmp(props.deviceName, info->subrenderer)))
|
||
{
|
||
Con_Printf(CON_WARNING"vulkan: attempting to use device \"%s\" despite no device queues being able to present to window surface\n", props.deviceName);
|
||
ignorequeuebugs = true;
|
||
}
|
||
else
|
||
{
|
||
//no queues can present to that surface, so I guess we can't use that device
|
||
Con_DLPrintf((wantdev != i)?1:0, "vulkan: ignoring device \"%s\" as it can't present to window\n", props.deviceName);
|
||
continue;
|
||
}
|
||
}
|
||
}
|
||
Con_DPrintf("Found Vulkan Device \"%s\"\n", props.deviceName);
|
||
|
||
if (!vk.gpu)
|
||
{
|
||
gpuidx = i;
|
||
vk.gpu = devs[i];
|
||
}
|
||
switch(props.deviceType)
|
||
{
|
||
default:
|
||
case VK_PHYSICAL_DEVICE_TYPE_OTHER:
|
||
pri = 5;
|
||
break;
|
||
case VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU:
|
||
pri = 2;
|
||
break;
|
||
case VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU:
|
||
pri = 1;
|
||
break;
|
||
case VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU:
|
||
pri = 3;
|
||
break;
|
||
case VK_PHYSICAL_DEVICE_TYPE_CPU:
|
||
pri = 4;
|
||
break;
|
||
}
|
||
if (vrsetup.vk.physicaldevice != VK_NULL_HANDLE)
|
||
{ //if we're using vr, use the gpu our vr context requires.
|
||
if (devs[i] == vrsetup.vk.physicaldevice)
|
||
pri = 0;
|
||
}
|
||
else if (wantdev >= 0)
|
||
{
|
||
if (wantdev == i)
|
||
pri = 0;
|
||
}
|
||
else
|
||
{
|
||
if (!Q_strcasecmp(props.deviceName, info->subrenderer))
|
||
pri = 0;
|
||
}
|
||
|
||
if (pri < bestpri)
|
||
{
|
||
gpuidx = i;
|
||
vk.gpu = devs[gpuidx];
|
||
bestpri = pri;
|
||
}
|
||
}
|
||
free(devs);
|
||
|
||
if (!vk.gpu)
|
||
{
|
||
Con_Printf(CON_ERROR"vulkan: unable to pick a usable device\n");
|
||
return false;
|
||
}
|
||
}
|
||
|
||
{
|
||
char *vendor, *type;
|
||
VkPhysicalDeviceProperties props;
|
||
vkGetPhysicalDeviceProperties(vk.gpu, &props);
|
||
|
||
switch(props.vendorID)
|
||
{
|
||
//explicit registered vendors
|
||
case 0x10001: vendor = "Vivante"; break;
|
||
case 0x10002: vendor = "VeriSilicon"; break;
|
||
case 0x10003: vendor = "Kazan"; break;
|
||
case 0x10004: vendor = "Codeplay"; break;
|
||
case /*VK_VENDOR_ID_MESA*/0x10005: vendor = "MESA"; break;
|
||
|
||
//pci vendor ids
|
||
//there's a lot of pci vendors, some even still exist, but not all of them actually have 3d hardware.
|
||
//many of these probably won't even be used... Oh well.
|
||
//anyway, here's some of the ones that are listed
|
||
case 0x1002: vendor = "AMD"; break;
|
||
case 0x10DE: vendor = "NVIDIA"; break;
|
||
case 0x8086: vendor = "Intel"; break; //cute
|
||
case 0x13B5: vendor = "ARM"; break;
|
||
case 0x5143: vendor = "Qualcomm"; break;
|
||
case 0x1AEE: vendor = "Imagination";break;
|
||
case 0x1957: vendor = "Freescale"; break;
|
||
|
||
//I really have no idea who makes mobile gpus nowadays, but lets make some guesses.
|
||
case 0x1AE0: vendor = "Google"; break;
|
||
case 0x5333: vendor = "S3"; break;
|
||
case 0xA200: vendor = "NEC"; break;
|
||
case 0x0A5C: vendor = "Broadcom"; break;
|
||
case 0x1131: vendor = "NXP"; break;
|
||
case 0x1099: vendor = "Samsung"; break;
|
||
case 0x10C3: vendor = "Samsung"; break;
|
||
case 0x11E2: vendor = "Samsung"; break;
|
||
case 0x1249: vendor = "Samsung"; break;
|
||
|
||
default: vendor = va("VEND_%x", props.vendorID); break;
|
||
}
|
||
|
||
switch(props.deviceType)
|
||
{
|
||
default:
|
||
case VK_PHYSICAL_DEVICE_TYPE_OTHER: type = "(other)"; break;
|
||
case VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU: type = "integrated"; break;
|
||
case VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU: type = "discrete"; break;
|
||
case VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU: type = "virtual"; break;
|
||
case VK_PHYSICAL_DEVICE_TYPE_CPU: type = "software"; break;
|
||
}
|
||
|
||
Con_TPrintf("Vulkan %u.%u.%u: GPU%i %s %s %s (%u.%u.%u)\n", VK_VERSION_MAJOR(props.apiVersion), VK_VERSION_MINOR(props.apiVersion), VK_VERSION_PATCH(props.apiVersion),
|
||
gpuidx, type, vendor, props.deviceName,
|
||
VK_VERSION_MAJOR(props.driverVersion), VK_VERSION_MINOR(props.driverVersion), VK_VERSION_PATCH(props.driverVersion)
|
||
);
|
||
}
|
||
|
||
//figure out which of the device's queue's we're going to use
|
||
{
|
||
uint32_t queue_count, i;
|
||
vkGetPhysicalDeviceQueueFamilyProperties(vk.gpu, &queue_count, NULL);
|
||
queueprops = malloc(sizeof(VkQueueFamilyProperties)*queue_count); //Oh how I wish I was able to use C99.
|
||
vkGetPhysicalDeviceQueueFamilyProperties(vk.gpu, &queue_count, queueprops);
|
||
|
||
vk.queuefam[0] = ~0u;
|
||
vk.queuefam[1] = ~0u;
|
||
vk.queuenum[0] = 0;
|
||
vk.queuenum[1] = 0;
|
||
|
||
/*
|
||
//try to find a 'dedicated' present queue
|
||
for (i = 0; i < queue_count; i++)
|
||
{
|
||
VkBool32 supportsPresent = FALSE;
|
||
VkAssert(vkGetPhysicalDeviceSurfaceSupportKHR(vk.gpu, i, vk.surface, &supportsPresent));
|
||
|
||
if (supportsPresent && !(queueprops[i].queueFlags & VK_QUEUE_GRAPHICS_BIT))
|
||
{
|
||
vk.queuefam[1] = i;
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (vk.queuefam[1] != ~0u)
|
||
{ //try to find a good graphics queue
|
||
for (i = 0; i < queue_count; i++)
|
||
{
|
||
if (queueprops[i].queueFlags & VK_QUEUE_GRAPHICS_BIT)
|
||
{
|
||
vk.queuefam[0] = i;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
else*/
|
||
{
|
||
for (i = 0; i < queue_count; i++)
|
||
{
|
||
VkBool32 supportsPresent = false;
|
||
if (!vk.khr_swapchain)
|
||
supportsPresent = true; //won't be used anyway.
|
||
else
|
||
VkAssert(vkGetPhysicalDeviceSurfaceSupportKHR(vk.gpu, i, vk.surface, &supportsPresent));
|
||
|
||
if ((queueprops[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) && supportsPresent)
|
||
{
|
||
vk.queuefam[0] = i;
|
||
vk.queuefam[1] = i;
|
||
break;
|
||
}
|
||
else if (vk.queuefam[0] == ~0u && (queueprops[i].queueFlags & VK_QUEUE_GRAPHICS_BIT))
|
||
vk.queuefam[0] = i;
|
||
else if (vk.queuefam[1] == ~0u && supportsPresent)
|
||
vk.queuefam[1] = i;
|
||
}
|
||
}
|
||
|
||
|
||
if (vk.queuefam[0] == ~0u || vk.queuefam[1] == ~0u)
|
||
{
|
||
if (ignorequeuebugs && queue_count>0)
|
||
{
|
||
vk.queuefam[0] = vk.queuefam[1] = 0;
|
||
}
|
||
else
|
||
{
|
||
free(queueprops);
|
||
Con_Printf(CON_ERROR"vulkan: unable to find suitable queues\n");
|
||
return false;
|
||
}
|
||
}
|
||
}
|
||
|
||
{
|
||
uint32_t extcount = 0, i;
|
||
VkExtensionProperties *ext;
|
||
vkEnumerateDeviceExtensionProperties(vk.gpu, NULL, &extcount, NULL);
|
||
ext = malloc(sizeof(*ext)*extcount);
|
||
vkEnumerateDeviceExtensionProperties(vk.gpu, NULL, &extcount, ext);
|
||
for (i = 0; i < extcount; i++)
|
||
Con_DLPrintf(2, " vkd: %s\n", ext[i].extensionName);
|
||
while (extcount --> 0)
|
||
{
|
||
for (e = 0; e < countof(knowndevexts); e++)
|
||
{
|
||
if (!strcmp(ext[extcount].extensionName, knowndevexts[e].name))
|
||
{
|
||
if (knowndevexts[e].var)
|
||
*knowndevexts[e].flag = !!knowndevexts[e].var->ival || (!*knowndevexts[e].var->string && knowndevexts[e].def);
|
||
knowndevexts[e].supported = true;
|
||
}
|
||
}
|
||
}
|
||
free(ext);
|
||
}
|
||
{
|
||
const char *devextensions[1+countof(knowndevexts)];
|
||
size_t numdevextensions = 0;
|
||
float queue_priorities[2] = {0.8, 1.0};
|
||
VkDeviceQueueCreateInfo queueinf[2] = {{VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO},{VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO}};
|
||
VkDeviceCreateInfo devinf = {VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO};
|
||
VkPhysicalDeviceFeatures features;
|
||
VkPhysicalDeviceFeatures avail;
|
||
memset(&features, 0, sizeof(features));
|
||
|
||
vkGetPhysicalDeviceFeatures(vk.gpu, &avail);
|
||
|
||
//try to enable whatever we can use, if we can.
|
||
features.robustBufferAccess = avail.robustBufferAccess;
|
||
features.textureCompressionBC = avail.textureCompressionBC;
|
||
features.textureCompressionETC2 = avail.textureCompressionETC2;
|
||
features.textureCompressionASTC_LDR = avail.textureCompressionASTC_LDR;
|
||
features.samplerAnisotropy = avail.samplerAnisotropy;
|
||
features.geometryShader = avail.geometryShader;
|
||
features.tessellationShader = avail.tessellationShader;
|
||
|
||
//Add in the extensions we support
|
||
for (e = 0; e < countof(knowndevexts); e++)
|
||
{ //prints are to let the user know what's going on. only warn if its explicitly enabled
|
||
if (knowndevexts[e].superseeded && *knowndevexts[e].superseeded)
|
||
{
|
||
Con_DPrintf("Superseeded %s.\n", knowndevexts[e].name);
|
||
*knowndevexts[e].flag = false;
|
||
}
|
||
else if (*knowndevexts[e].flag)
|
||
{
|
||
Con_DPrintf("Using %s.\n", knowndevexts[e].name);
|
||
devextensions[numdevextensions++] = knowndevexts[e].name;
|
||
}
|
||
else if (knowndevexts[e].var && knowndevexts[e].var->ival)
|
||
Con_Printf("unable to enable %s extension.%s\n", knowndevexts[e].name, knowndevexts[e].warningtext?knowndevexts[e].warningtext:"");
|
||
else if (knowndevexts[e].supported)
|
||
Con_DPrintf("Ignoring %s.\n", knowndevexts[e].name);
|
||
else
|
||
Con_DPrintf("Unavailable %s.\n", knowndevexts[e].name);
|
||
}
|
||
|
||
queueinf[0].pNext = NULL;
|
||
queueinf[0].queueFamilyIndex = vk.queuefam[0];
|
||
queueinf[0].queueCount = 1;
|
||
queueinf[0].pQueuePriorities = &queue_priorities[0];
|
||
queueinf[1].pNext = NULL;
|
||
queueinf[1].queueFamilyIndex = vk.queuefam[1];
|
||
queueinf[1].queueCount = 1;
|
||
queueinf[1].pQueuePriorities = &queue_priorities[1];
|
||
|
||
if (vk.queuefam[0] == vk.queuefam[1])
|
||
{
|
||
devinf.queueCreateInfoCount = 1;
|
||
|
||
if (queueprops[queueinf[0].queueFamilyIndex].queueCount >= 2 && vk_dualqueue.ival)
|
||
{
|
||
queueinf[0].queueCount = 2;
|
||
vk.queuenum[1] = 1;
|
||
Con_DPrintf("Using duel queue\n");
|
||
}
|
||
else
|
||
{
|
||
queueinf[0].queueCount = 1;
|
||
if (vk.khr_swapchain)
|
||
vk.dopresent = VK_DoPresent; //can't split submit+present onto different queues, so do these on a single thread.
|
||
Con_DPrintf("Using single queue\n");
|
||
}
|
||
}
|
||
else
|
||
{
|
||
devinf.queueCreateInfoCount = 2;
|
||
Con_DPrintf("Using separate queue families\n");
|
||
}
|
||
|
||
free(queueprops);
|
||
|
||
devinf.pQueueCreateInfos = queueinf;
|
||
devinf.enabledLayerCount = vklayercount;
|
||
devinf.ppEnabledLayerNames = vklayerlist;
|
||
devinf.enabledExtensionCount = numdevextensions;
|
||
devinf.ppEnabledExtensionNames = devextensions;
|
||
devinf.pEnabledFeatures = &features;
|
||
|
||
#if 0
|
||
if (vkEnumeratePhysicalDeviceGroupsKHR && vk_afr.ival)
|
||
{
|
||
//'Every physical device must be in exactly one device group'. So we can just use the first group that lists it and automatically get AFR.
|
||
uint32_t gpugroups = 0;
|
||
VkDeviceGroupDeviceCreateInfoKHX dgdci = {VK_STRUCTURE_TYPE_DEVICE_GROUP_DEVICE_CREATE_INFO_KHR};
|
||
|
||
VkPhysicalDeviceGroupPropertiesKHR *groups;
|
||
vkEnumeratePhysicalDeviceGroupsKHR(vk.instance, &gpugroups, NULL);
|
||
groups = malloc(sizeof(*groups)*gpugroups);
|
||
vkEnumeratePhysicalDeviceGroupsKHR(vk.instance, &gpugroups, groups);
|
||
for (i = 0; i < gpugroups; i++)
|
||
{
|
||
for (j = 0; j < groups[i].physicalDeviceCount; j++)
|
||
if (groups[i].physicalDevices[j] == vk.gpu)
|
||
{
|
||
dgdci.physicalDeviceCount = groups[i].physicalDeviceCount;
|
||
dgdci.pPhysicalDevices = groups[i].physicalDevices;
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (dgdci.physicalDeviceCount > 1)
|
||
{
|
||
vk.subdevices = dgdci.physicalDeviceCount;
|
||
dgdci.pNext = devinf.pNext;
|
||
devinf.pNext = &dgdci;
|
||
}
|
||
|
||
err = vkCreateDevice(vk.gpu, &devinf, NULL, &vk.device);
|
||
|
||
free(groups);
|
||
}
|
||
else
|
||
#endif
|
||
err = vkCreateDevice(vk.gpu, &devinf, NULL, &vk.device);
|
||
|
||
switch(err)
|
||
{
|
||
case VK_ERROR_INCOMPATIBLE_DRIVER:
|
||
Con_TPrintf(CON_ERROR"VK_ERROR_INCOMPATIBLE_DRIVER: please install an appropriate vulkan driver\n");
|
||
return false;
|
||
case VK_ERROR_EXTENSION_NOT_PRESENT:
|
||
case VK_ERROR_FEATURE_NOT_PRESENT:
|
||
case VK_ERROR_INITIALIZATION_FAILED:
|
||
case VK_ERROR_DEVICE_LOST:
|
||
case VK_ERROR_OUT_OF_HOST_MEMORY:
|
||
case VK_ERROR_OUT_OF_DEVICE_MEMORY:
|
||
Con_Printf(CON_ERROR"%s: something on a system level is probably misconfigured\n", VK_VKErrorToString(err));
|
||
return false;
|
||
default:
|
||
Con_Printf(CON_ERROR"Unknown vulkan device creation error: %x\n", err);
|
||
return false;
|
||
case VK_SUCCESS:
|
||
break;
|
||
}
|
||
}
|
||
|
||
#ifdef VK_NO_PROTOTYPES
|
||
vkGetDeviceProcAddr = (PFN_vkGetDeviceProcAddr)vkGetInstanceProcAddr(vk.instance, "vkGetDeviceProcAddr");
|
||
#define VKFunc(n) vk##n = (PFN_vk##n)vkGetDeviceProcAddr(vk.device, "vk"#n);
|
||
VKDevFuncs
|
||
#undef VKFunc
|
||
#endif
|
||
|
||
vkGetDeviceQueue(vk.device, vk.queuefam[0], vk.queuenum[0], &vk.queue_render);
|
||
vkGetDeviceQueue(vk.device, vk.queuefam[1], vk.queuenum[1], &vk.queue_present);
|
||
|
||
vrsetup.vk.instance = vk.instance;
|
||
vrsetup.vk.device = vk.device;
|
||
vrsetup.vk.physicaldevice = vk.gpu;
|
||
vrsetup.vk.queuefamily = vk.queuefam[1];
|
||
vrsetup.vk.queueindex = vk.queuenum[1];
|
||
if (vid.vr)
|
||
{
|
||
if (!vid.vr->Init(&vrsetup, info))
|
||
{
|
||
vid.vr->Shutdown();
|
||
vid.vr = NULL;
|
||
}
|
||
}
|
||
|
||
vkGetPhysicalDeviceMemoryProperties(vk.gpu, &vk.memory_properties);
|
||
|
||
{
|
||
VkCommandPoolCreateInfo cpci = {VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO};
|
||
cpci.queueFamilyIndex = vk.queuefam[0];
|
||
cpci.flags = VK_COMMAND_POOL_CREATE_TRANSIENT_BIT|VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
|
||
VkAssert(vkCreateCommandPool(vk.device, &cpci, vkallocationcb, &vk.cmdpool));
|
||
}
|
||
|
||
|
||
sh_config.progpath = "vulkan/%s.fvb";
|
||
sh_config.blobpath = NULL; //just use general pipeline cache instead.
|
||
sh_config.shadernamefmt = NULL;//"_vulkan";
|
||
|
||
sh_config.progs_supported = true;
|
||
sh_config.progs_required = true;
|
||
sh_config.minver = -1;
|
||
sh_config.maxver = -1;
|
||
|
||
sh_config.texture_allow_block_padding = true;
|
||
sh_config.texture_non_power_of_two = true; //is this always true?
|
||
sh_config.texture_non_power_of_two_pic = true; //probably true...
|
||
sh_config.npot_rounddown = false;
|
||
sh_config.tex_env_combine = false; //fixme: figure out what this means...
|
||
sh_config.nv_tex_env_combine4 = false; //fixme: figure out what this means...
|
||
sh_config.env_add = false; //fixme: figure out what this means...
|
||
|
||
sh_config.can_mipcap = true;
|
||
sh_config.havecubemaps = true;
|
||
|
||
VK_CheckTextureFormats();
|
||
|
||
|
||
sh_config.pDeleteProg = NULL;
|
||
sh_config.pLoadBlob = NULL;
|
||
sh_config.pCreateProgram = NULL;
|
||
sh_config.pValidateProgram = NULL;
|
||
sh_config.pProgAutoFields = NULL;
|
||
|
||
if (info->depthbits == 16 && sh_config.texfmt[PTI_DEPTH16])
|
||
vk.depthformat = VK_FORMAT_D16_UNORM;
|
||
else if (info->depthbits == 32 && sh_config.texfmt[PTI_DEPTH32])
|
||
vk.depthformat = VK_FORMAT_D32_SFLOAT;
|
||
// else if (info->depthbits == 32 && sh_config.texfmt[PTI_DEPTH32_8])
|
||
// vk.depthformat = VK_FORMAT_D32_SFLOAT_S8_UINT;
|
||
else if (info->depthbits == 24 && sh_config.texfmt[PTI_DEPTH24_8])
|
||
vk.depthformat = VK_FORMAT_D24_UNORM_S8_UINT;
|
||
else if (info->depthbits == 24 && sh_config.texfmt[PTI_DEPTH24])
|
||
vk.depthformat = VK_FORMAT_X8_D24_UNORM_PACK32;
|
||
|
||
else if (sh_config.texfmt[PTI_DEPTH24])
|
||
vk.depthformat = VK_FORMAT_X8_D24_UNORM_PACK32;
|
||
else if (sh_config.texfmt[PTI_DEPTH24_8])
|
||
vk.depthformat = VK_FORMAT_D24_UNORM_S8_UINT;
|
||
else if (sh_config.texfmt[PTI_DEPTH32]) //nvidia: "Don’t use 32-bit floating point depth formats, due to the performance cost, unless improved precision is actually required"
|
||
vk.depthformat = VK_FORMAT_D32_SFLOAT;
|
||
else //16bit depth is guarenteed in vulkan
|
||
vk.depthformat = VK_FORMAT_D16_UNORM;
|
||
|
||
#ifdef MULTITHREAD
|
||
vk.submitcondition = Sys_CreateConditional();
|
||
#endif
|
||
|
||
{
|
||
VkPipelineCacheCreateInfo pci = {VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO};
|
||
qofs_t size = 0;
|
||
pci.pInitialData = FS_MallocFile("vulkan.pcache", FS_ROOT, &size);
|
||
pci.initialDataSize = size;
|
||
VkAssert(vkCreatePipelineCache(vk.device, &pci, vkallocationcb, &vk.pipelinecache));
|
||
FS_FreeFile((void*)pci.pInitialData);
|
||
}
|
||
|
||
if (VK_CreateSwapChain())
|
||
{
|
||
vk.neednewswapchain = false;
|
||
|
||
#ifdef MULTITHREAD
|
||
if (vk.allowsubmissionthread && (vk_submissionthread.ival || !*vk_submissionthread.string))
|
||
{
|
||
vk.submitthread = Sys_CreateThread("vksubmission", VK_Submit_Thread, NULL, THREADP_HIGHEST, 0);
|
||
}
|
||
#endif
|
||
}
|
||
if (info->srgb > 0 && (vid.flags & VID_SRGB_FB))
|
||
vid.flags |= VID_SRGBAWARE;
|
||
|
||
return true;
|
||
}
|
||
void VK_Shutdown(void)
|
||
{
|
||
uint32_t i;
|
||
|
||
VK_DestroySwapChain();
|
||
|
||
for (i = 0; i < countof(postproc); i++)
|
||
VKBE_RT_Gen(&postproc[i], NULL, 0, 0, false, RT_IMAGEFLAGS);
|
||
VKBE_RT_Gen_Cube(&vk_rt_cubemap, 0, false);
|
||
VK_R_BloomShutdown();
|
||
|
||
if (vk.cmdpool)
|
||
vkDestroyCommandPool(vk.device, vk.cmdpool, vkallocationcb);
|
||
VK_DestroyRenderPasses();
|
||
|
||
if (vk.pipelinecache)
|
||
{
|
||
size_t size;
|
||
if (VK_SUCCESS == vkGetPipelineCacheData(vk.device, vk.pipelinecache, &size, NULL))
|
||
{
|
||
void *ptr = Z_Malloc(size); //valgrind says nvidia isn't initialising this.
|
||
if (VK_SUCCESS == vkGetPipelineCacheData(vk.device, vk.pipelinecache, &size, ptr))
|
||
FS_WriteFile("vulkan.pcache", ptr, size, FS_ROOT);
|
||
Z_Free(ptr);
|
||
}
|
||
vkDestroyPipelineCache(vk.device, vk.pipelinecache, vkallocationcb);
|
||
vk.pipelinecache = VK_NULL_HANDLE;
|
||
}
|
||
|
||
while(vk.mempools)
|
||
{
|
||
void *l;
|
||
vkFreeMemory(vk.device, vk.mempools->memory, vkallocationcb);
|
||
l = vk.mempools;
|
||
vk.mempools = vk.mempools->next;
|
||
Z_Free(l);
|
||
}
|
||
|
||
if (vk.device)
|
||
vkDestroyDevice(vk.device, vkallocationcb);
|
||
#ifdef VK_EXT_debug_utils
|
||
if (vk_debugucallback)
|
||
{
|
||
vkDestroyDebugUtilsMessengerEXT(vk.instance, vk_debugucallback, vkallocationcb);
|
||
vk_debugucallback = VK_NULL_HANDLE;
|
||
}
|
||
#endif
|
||
#ifdef VK_EXT_debug_report
|
||
if (vk_debugcallback)
|
||
{
|
||
vkDestroyDebugReportCallbackEXT(vk.instance, vk_debugcallback, vkallocationcb);
|
||
vk_debugcallback = VK_NULL_HANDLE;
|
||
}
|
||
#endif
|
||
|
||
if (vk.surface)
|
||
vkDestroySurfaceKHR(vk.instance, vk.surface, vkallocationcb);
|
||
if (vk.instance)
|
||
vkDestroyInstance(vk.instance, vkallocationcb);
|
||
#ifdef MULTITHREAD
|
||
if (vk.submitcondition)
|
||
Sys_DestroyConditional(vk.submitcondition);
|
||
#endif
|
||
|
||
memset(&vk, 0, sizeof(vk));
|
||
|
||
#ifdef VK_NO_PROTOTYPES
|
||
#define VKFunc(n) vk##n = NULL;
|
||
VKFuncs
|
||
#undef VKFunc
|
||
#endif
|
||
|
||
}
|
||
#endif
|