mirror of
https://github.com/id-Software/DOOM-3-BFG.git
synced 2025-03-14 06:34:10 +00:00
Removed more obsolete Vulkan & OpenGL code
This commit is contained in:
parent
9121f84559
commit
8975f59f62
7 changed files with 10 additions and 623 deletions
|
@ -6288,17 +6288,6 @@ void idRenderBackend::DrawView( const void* data, const int stereoEye )
|
|||
return;
|
||||
}
|
||||
|
||||
// skip render context sets the wgl context to NULL,
|
||||
// which should factor out the API cost, under the assumption
|
||||
// that all gl calls just return if the context isn't valid
|
||||
|
||||
// RB: not really needed
|
||||
//if( r_skipRenderContext.GetBool() && backEnd.viewDef->viewEntitys )
|
||||
//{
|
||||
// GLimp_DeactivateContext();
|
||||
//}
|
||||
// RB end
|
||||
|
||||
pc.c_surfaces += viewDef->numDrawSurfs;
|
||||
|
||||
DBG_ShowOverdraw();
|
||||
|
@ -6306,7 +6295,9 @@ void idRenderBackend::DrawView( const void* data, const int stereoEye )
|
|||
// render the scene
|
||||
DrawViewInternal( cmd->viewDef, stereoEye );
|
||||
|
||||
MotionBlur();
|
||||
// RB: Support motion blur in the future again?
|
||||
// It is the worst thing next to depth of field
|
||||
//MotionBlur();
|
||||
|
||||
// optionally draw a box colored based on the eye number
|
||||
if( r_drawEyeColor.GetBool() )
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
Doom 3 BFG Edition GPL Source Code
|
||||
Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company.
|
||||
Copyright (C) 2016-2017 Dustin Land
|
||||
Copyright (C) 2017-2020 Robert Beckebans
|
||||
Copyright (C) 2017-2023 Robert Beckebans
|
||||
Copyright (C) 2022 Stephen Pridham
|
||||
|
||||
This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").
|
||||
|
@ -125,139 +125,6 @@ struct debugPolygon_t
|
|||
|
||||
void RB_SetMVP( const idRenderMatrix& mvp );
|
||||
void RB_SetVertexColorParms( stageVertexColor_t svc );
|
||||
//void RB_GetShaderTextureMatrix( const float* shaderRegisters, const textureStage_t* texture, float matrix[16] );
|
||||
//void RB_LoadShaderTextureMatrix( const float* shaderRegisters, const textureStage_t* texture );
|
||||
//void RB_BakeTextureMatrixIntoTexgen( idPlane lightProject[3], const float* textureMatrix );
|
||||
|
||||
//bool ChangeDisplaySettingsIfNeeded( gfxImpParms_t parms );
|
||||
//bool CreateGameWindow( gfxImpParms_t parms );
|
||||
|
||||
#if defined( USE_VULKAN )
|
||||
|
||||
// SRS - Generalized Vulkan SDL platform
|
||||
#if defined( VULKAN_USE_PLATFORM_SDL )
|
||||
#include <SDL.h>
|
||||
#include <SDL_vulkan.h>
|
||||
#endif
|
||||
|
||||
struct gpuInfo_t
|
||||
{
|
||||
VkPhysicalDevice device;
|
||||
VkPhysicalDeviceProperties props;
|
||||
VkPhysicalDeviceMemoryProperties memProps;
|
||||
VkSurfaceCapabilitiesKHR surfaceCaps;
|
||||
idList< VkSurfaceFormatKHR > surfaceFormats;
|
||||
idList< VkPresentModeKHR > presentModes;
|
||||
idList< VkQueueFamilyProperties > queueFamilyProps;
|
||||
idList< VkExtensionProperties > extensionProps;
|
||||
};
|
||||
|
||||
struct vulkanContext_t
|
||||
{
|
||||
// Eric: If on linux, use this to pass SDL_Window pointer to the SDL_Vulkan_* methods not in sdl_vkimp.cpp file.
|
||||
// SRS - Generalized Vulkan SDL platform
|
||||
#if defined( VULKAN_USE_PLATFORM_SDL )
|
||||
SDL_Window* sdlWindow = nullptr;
|
||||
#endif
|
||||
uint64 frameCounter;
|
||||
uint32 frameParity;
|
||||
|
||||
vertCacheHandle_t jointCacheHandle;
|
||||
|
||||
VkInstance instance;
|
||||
|
||||
// selected physical device
|
||||
VkPhysicalDevice physicalDevice;
|
||||
VkPhysicalDeviceFeatures physicalDeviceFeatures;
|
||||
|
||||
// logical device
|
||||
VkDevice device;
|
||||
|
||||
VkQueue graphicsQueue;
|
||||
VkQueue presentQueue;
|
||||
int graphicsFamilyIdx;
|
||||
int presentFamilyIdx;
|
||||
VkDebugReportCallbackEXT callback;
|
||||
|
||||
idList< const char* > instanceExtensions;
|
||||
idList< const char* > deviceExtensions;
|
||||
idList< const char* > validationLayers;
|
||||
|
||||
bool debugMarkerSupportAvailable;
|
||||
bool debugUtilsSupportAvailable;
|
||||
bool deviceProperties2Available; // SRS - For getting device properties in support of gfxInfo
|
||||
|
||||
// selected GPU
|
||||
gpuInfo_t* gpu;
|
||||
|
||||
// all GPUs found on the system
|
||||
idList< gpuInfo_t > gpus;
|
||||
|
||||
VkCommandPool commandPool;
|
||||
idArray< VkCommandBuffer, NUM_FRAME_DATA > commandBuffer;
|
||||
idArray< VkFence, NUM_FRAME_DATA > commandBufferFences;
|
||||
idArray< bool, NUM_FRAME_DATA > commandBufferRecorded;
|
||||
|
||||
VkSurfaceKHR surface;
|
||||
VkPresentModeKHR presentMode;
|
||||
VkFormat depthFormat;
|
||||
VkRenderPass renderPass;
|
||||
VkPipelineCache pipelineCache;
|
||||
VkSampleCountFlagBits sampleCount;
|
||||
bool supersampling;
|
||||
|
||||
int fullscreen;
|
||||
VkSwapchainKHR swapchain;
|
||||
VkFormat swapchainFormat;
|
||||
VkExtent2D swapchainExtent;
|
||||
uint32 currentSwapIndex;
|
||||
VkImage msaaImage;
|
||||
VkImageView msaaImageView;
|
||||
#if defined( USE_AMD_ALLOCATOR )
|
||||
VmaAllocation msaaVmaAllocation;
|
||||
VmaAllocationInfo msaaAllocation;
|
||||
#else
|
||||
vulkanAllocation_t msaaAllocation;
|
||||
#endif
|
||||
idArray< VkImage, NUM_FRAME_DATA > swapchainImages;
|
||||
idArray< VkImageView, NUM_FRAME_DATA > swapchainViews;
|
||||
|
||||
idArray< VkFramebuffer, NUM_FRAME_DATA > frameBuffers;
|
||||
idArray< VkSemaphore, NUM_FRAME_DATA > acquireSemaphores;
|
||||
idArray< VkSemaphore, NUM_FRAME_DATA > renderCompleteSemaphores;
|
||||
|
||||
int currentImageParm;
|
||||
idArray< idImage*, MAX_IMAGE_PARMS > imageParms;
|
||||
|
||||
//typedef uint32 QueryTuple[2];
|
||||
|
||||
// GPU timestamp queries
|
||||
idArray< uint32, NUM_FRAME_DATA > queryIndex;
|
||||
idArray< idArray< uint32, MRB_TOTAL_QUERIES >, NUM_FRAME_DATA > queryAssignedIndex;
|
||||
idArray< idArray< uint64, NUM_TIMESTAMP_QUERIES >, NUM_FRAME_DATA > queryResults;
|
||||
idArray< VkQueryPool, NUM_FRAME_DATA > queryPools;
|
||||
};
|
||||
|
||||
extern vulkanContext_t vkcontext;
|
||||
|
||||
#elif !defined( USE_NVRHI )
|
||||
|
||||
struct glContext_t
|
||||
{
|
||||
uint64 frameCounter;
|
||||
uint32 frameParity;
|
||||
|
||||
tmu_t tmu[ MAX_MULTITEXTURE_UNITS ];
|
||||
uint64 stencilOperations[ STENCIL_FACE_NUM ];
|
||||
|
||||
// for GL_TIME_ELAPSED_EXT queries
|
||||
GLuint renderLogMainBlockTimeQueryIds[ NUM_FRAME_DATA ][ MRB_TOTAL_QUERIES ];
|
||||
uint32 renderLogMainBlockTimeQueryIssued[ NUM_FRAME_DATA ][ MRB_TOTAL_QUERIES ];
|
||||
};
|
||||
|
||||
extern glContext_t glcontext;
|
||||
|
||||
#endif
|
||||
|
||||
/*
|
||||
===========================================================================
|
||||
|
@ -520,8 +387,6 @@ private:
|
|||
TileMap tileMap;
|
||||
|
||||
private:
|
||||
#if defined( USE_NVRHI )
|
||||
|
||||
idScreenRect stateViewport;
|
||||
idScreenRect stateScissor;
|
||||
|
||||
|
@ -569,25 +434,6 @@ public:
|
|||
{
|
||||
return commonPasses;
|
||||
}
|
||||
#elif !defined( USE_VULKAN )
|
||||
int currenttmu;
|
||||
|
||||
unsigned int currentVertexBuffer;
|
||||
unsigned int currentIndexBuffer;
|
||||
Framebuffer* currentFramebuffer; // RB: for offscreen rendering
|
||||
|
||||
vertexLayoutType_t vertexLayout;
|
||||
|
||||
float polyOfsScale;
|
||||
float polyOfsBias;
|
||||
|
||||
public:
|
||||
int GetCurrentTextureUnit() const
|
||||
{
|
||||
return currenttmu;
|
||||
}
|
||||
|
||||
#endif // !defined( USE_VULKAN )
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
@ -1167,7 +1167,6 @@ extern idCVar r_skipFrontEnd; // bypasses all front end work, but 2D gui rend
|
|||
extern idCVar r_skipBackEnd; // don't draw anything
|
||||
extern idCVar r_skipCopyTexture; // do all rendering, but don't actually copyTexSubImage2D
|
||||
extern idCVar r_skipRender; // skip 3D rendering, but pass 2D
|
||||
extern idCVar r_skipRenderContext; // NULL the rendering context during backend 3D rendering
|
||||
extern idCVar r_skipTranslucent; // skip the translucent interaction rendering
|
||||
extern idCVar r_skipAmbient; // bypasses all non-interaction drawing
|
||||
extern idCVar r_skipNewAmbient; // bypasses all vertex/fragment program ambients
|
||||
|
|
|
@ -29,18 +29,8 @@ If you have questions concerning this license or the applicable additional terms
|
|||
#include "precompiled.h"
|
||||
#pragma hdrstop
|
||||
|
||||
#if defined( USE_NVRHI )
|
||||
#include <sys/DeviceManager.h>
|
||||
extern DeviceManager* deviceManager;
|
||||
#endif
|
||||
|
||||
/*
|
||||
================================================================================================
|
||||
Contains the RenderLog implementation.
|
||||
|
||||
TODO: Emit statistics to the logfile at the end of views and frames.
|
||||
================================================================================================
|
||||
*/
|
||||
#include <sys/DeviceManager.h>
|
||||
extern DeviceManager* deviceManager;
|
||||
|
||||
idCVar r_logLevel( "r_logLevel", "0", CVAR_INTEGER, "1 = blocks only, 2 = everything", 0, 2 );
|
||||
|
||||
|
@ -69,42 +59,7 @@ const char* renderLogMainBlockLabels[] =
|
|||
ASSERT_ENUM_STRING( MRB_TOTAL, 17 )
|
||||
};
|
||||
|
||||
#if defined( USE_VULKAN )
|
||||
compile_time_assert( NUM_TIMESTAMP_QUERIES >= ( MRB_TOTAL_QUERIES ) );
|
||||
#endif
|
||||
|
||||
extern uint64 Sys_Microseconds();
|
||||
/*
|
||||
================================================================================================
|
||||
|
||||
PIX events on all platforms
|
||||
|
||||
================================================================================================
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
================================================
|
||||
pixEvent_t
|
||||
================================================
|
||||
*/
|
||||
struct pixEvent_t
|
||||
{
|
||||
char name[256];
|
||||
uint64 cpuTime;
|
||||
uint64 gpuTime;
|
||||
};
|
||||
|
||||
idCVar r_pix( "r_pix", "0", CVAR_INTEGER, "print GPU/CPU event timing" );
|
||||
|
||||
#if !defined( USE_VULKAN ) && !defined(USE_NVRHI)
|
||||
static const int MAX_PIX_EVENTS = 256;
|
||||
// defer allocation of this until needed, so we don't waste lots of memory
|
||||
pixEvent_t* pixEvents; // [MAX_PIX_EVENTS]
|
||||
int numPixEvents;
|
||||
int numPixLevels;
|
||||
static GLuint timeQueryIds[MAX_PIX_EVENTS];
|
||||
#endif
|
||||
|
||||
/*
|
||||
========================
|
||||
|
@ -120,86 +75,10 @@ void PC_BeginNamedEvent( const char* szName, const idVec4& color, nvrhi::IComman
|
|||
return;
|
||||
}
|
||||
|
||||
#if defined( USE_NVRHI )
|
||||
if( commandList )
|
||||
{
|
||||
commandList->beginMarker( szName );
|
||||
}
|
||||
|
||||
#elif defined( USE_VULKAN )
|
||||
|
||||
// start an annotated group of calls under the this name
|
||||
// SRS - Prefer VK_EXT_debug_utils over VK_EXT_debug_marker/VK_EXT_debug_report (deprecated by VK_EXT_debug_utils)
|
||||
if( vkcontext.debugUtilsSupportAvailable )
|
||||
{
|
||||
VkDebugUtilsLabelEXT label = {};
|
||||
label.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_LABEL_EXT;
|
||||
label.pLabelName = szName;
|
||||
label.color[0] = color.x;
|
||||
label.color[1] = color.y;
|
||||
label.color[2] = color.z;
|
||||
label.color[3] = color.w;
|
||||
|
||||
qvkCmdBeginDebugUtilsLabelEXT( vkcontext.commandBuffer[ vkcontext.frameParity ], &label );
|
||||
}
|
||||
else if( vkcontext.debugMarkerSupportAvailable )
|
||||
{
|
||||
VkDebugMarkerMarkerInfoEXT label = {};
|
||||
label.sType = VK_STRUCTURE_TYPE_DEBUG_MARKER_MARKER_INFO_EXT;
|
||||
label.pMarkerName = szName;
|
||||
label.color[0] = color.x;
|
||||
label.color[1] = color.y;
|
||||
label.color[2] = color.z;
|
||||
label.color[3] = color.w;
|
||||
|
||||
qvkCmdDebugMarkerBeginEXT( vkcontext.commandBuffer[ vkcontext.frameParity ], &label );
|
||||
}
|
||||
#else
|
||||
// RB: colors are not supported in OpenGL
|
||||
|
||||
// only do this if RBDOOM-3-BFG was started by RenderDoc or some similar tool
|
||||
if( glConfig.gremedyStringMarkerAvailable && glConfig.khronosDebugAvailable )
|
||||
{
|
||||
glPushDebugGroup( GL_DEBUG_SOURCE_APPLICATION_ARB, 0, GLsizei( strlen( szName ) ), szName );
|
||||
}
|
||||
#endif
|
||||
|
||||
#if 0
|
||||
if( !r_pix.GetBool() )
|
||||
{
|
||||
return;
|
||||
}
|
||||
if( !pixEvents )
|
||||
{
|
||||
// lazy allocation to not waste memory
|
||||
pixEvents = ( pixEvent_t* )Mem_ClearedAlloc( sizeof( *pixEvents ) * MAX_PIX_EVENTS, TAG_CRAP );
|
||||
}
|
||||
if( numPixEvents >= MAX_PIX_EVENTS )
|
||||
{
|
||||
idLib::FatalError( "PC_BeginNamedEvent: event overflow" );
|
||||
}
|
||||
if( ++numPixLevels > 1 )
|
||||
{
|
||||
return; // only get top level timing information
|
||||
}
|
||||
if( !glGetQueryObjectui64vEXT )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GL_CheckErrors();
|
||||
if( timeQueryIds[0] == 0 )
|
||||
{
|
||||
glGenQueries( MAX_PIX_EVENTS, timeQueryIds );
|
||||
}
|
||||
glFinish();
|
||||
glBeginQuery( GL_TIME_ELAPSED_EXT, timeQueryIds[numPixEvents] );
|
||||
GL_CheckErrors();
|
||||
|
||||
pixEvent_t* ev = &pixEvents[numPixEvents++];
|
||||
strncpy( ev->name, szName, sizeof( ev->name ) - 1 );
|
||||
ev->cpuTime = Sys_Microseconds();
|
||||
#endif
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -214,96 +93,12 @@ void PC_EndNamedEvent( nvrhi::ICommandList* commandList )
|
|||
return;
|
||||
}
|
||||
|
||||
#if defined( USE_NVRHI )
|
||||
if( commandList )
|
||||
{
|
||||
commandList->endMarker();
|
||||
}
|
||||
|
||||
#elif defined( USE_VULKAN )
|
||||
// SRS - Prefer VK_EXT_debug_utils over VK_EXT_debug_marker/VK_EXT_debug_report (deprecated by VK_EXT_debug_utils)
|
||||
if( vkcontext.debugUtilsSupportAvailable )
|
||||
{
|
||||
qvkCmdEndDebugUtilsLabelEXT( vkcontext.commandBuffer[ vkcontext.frameParity ] );
|
||||
}
|
||||
else if( vkcontext.debugMarkerSupportAvailable )
|
||||
{
|
||||
qvkCmdDebugMarkerEndEXT( vkcontext.commandBuffer[ vkcontext.frameParity ] );
|
||||
}
|
||||
#else
|
||||
// only do this if RBDOOM-3-BFG was started by RenderDoc or some similar tool
|
||||
if( glConfig.gremedyStringMarkerAvailable && glConfig.khronosDebugAvailable )
|
||||
{
|
||||
glPopDebugGroup();
|
||||
}
|
||||
#endif
|
||||
|
||||
#if 0
|
||||
if( !r_pix.GetBool() )
|
||||
{
|
||||
return;
|
||||
}
|
||||
if( numPixLevels <= 0 )
|
||||
{
|
||||
idLib::FatalError( "PC_EndNamedEvent: level underflow" );
|
||||
}
|
||||
if( --numPixLevels > 0 )
|
||||
{
|
||||
// only do timing on top level events
|
||||
return;
|
||||
}
|
||||
if( !glGetQueryObjectui64vEXT )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
pixEvent_t* ev = &pixEvents[numPixEvents - 1];
|
||||
ev->cpuTime = Sys_Microseconds() - ev->cpuTime;
|
||||
|
||||
GL_CheckErrors();
|
||||
glEndQuery( GL_TIME_ELAPSED_EXT );
|
||||
GL_CheckErrors();
|
||||
#endif
|
||||
}
|
||||
|
||||
/*
|
||||
========================
|
||||
PC_EndFrame
|
||||
========================
|
||||
*/
|
||||
void PC_EndFrame()
|
||||
{
|
||||
#if 0
|
||||
if( !r_pix.GetBool() )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int64 totalGPU = 0;
|
||||
int64 totalCPU = 0;
|
||||
|
||||
idLib::Printf( "----- GPU Events -----\n" );
|
||||
for( int i = 0 ; i < numPixEvents ; i++ )
|
||||
{
|
||||
pixEvent_t* ev = &pixEvents[i];
|
||||
|
||||
int64 gpuTime = 0;
|
||||
glGetQueryObjectui64vEXT( timeQueryIds[i], GL_QUERY_RESULT, ( GLuint64EXT* )&gpuTime );
|
||||
ev->gpuTime = gpuTime;
|
||||
|
||||
idLib::Printf( "%2d: %1.2f (GPU) %1.3f (CPU) = %s\n", i, ev->gpuTime / 1000000.0f, ev->cpuTime / 1000.0f, ev->name );
|
||||
totalGPU += ev->gpuTime;
|
||||
totalCPU += ev->cpuTime;
|
||||
}
|
||||
idLib::Printf( "%2d: %1.2f (GPU) %1.3f (CPU) = total\n", numPixEvents, totalGPU / 1000000.0f, totalCPU / 1000.0f );
|
||||
memset( pixEvents, 0, numPixLevels * sizeof( pixEvents[0] ) );
|
||||
|
||||
numPixEvents = 0;
|
||||
numPixLevels = 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
================================================================================================
|
||||
|
||||
|
@ -323,37 +118,29 @@ idRenderLog::idRenderLog()
|
|||
|
||||
void idRenderLog::Init()
|
||||
{
|
||||
#if defined( USE_NVRHI )
|
||||
for( int i = 0; i < MRB_TOTAL * NUM_FRAME_DATA; i++ )
|
||||
{
|
||||
timerQueries.Append( deviceManager->GetDevice()->createTimerQuery() );
|
||||
timerUsed.Append( false );
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void idRenderLog::Shutdown()
|
||||
{
|
||||
#if defined( USE_NVRHI )
|
||||
for( int i = 0; i < MRB_TOTAL * NUM_FRAME_DATA; i++ )
|
||||
{
|
||||
timerQueries[i].Reset();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void idRenderLog::StartFrame( nvrhi::ICommandList* _commandList )
|
||||
{
|
||||
#if defined( USE_NVRHI )
|
||||
commandList = _commandList;
|
||||
#endif
|
||||
}
|
||||
|
||||
void idRenderLog::EndFrame()
|
||||
{
|
||||
#if defined( USE_NVRHI )
|
||||
commandList = nullptr;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
|
@ -370,8 +157,6 @@ void idRenderLog::OpenMainBlock( renderLogMainBlock_t block )
|
|||
{
|
||||
mainBlock = block;
|
||||
|
||||
#if defined( USE_NVRHI )
|
||||
|
||||
int timerIndex = mainBlock + frameParity * MRB_TOTAL;
|
||||
|
||||
// SRS - Only issue a new start timer query if timer slot unused
|
||||
|
@ -379,41 +164,6 @@ void idRenderLog::OpenMainBlock( renderLogMainBlock_t block )
|
|||
{
|
||||
commandList->beginTimerQuery( timerQueries[ timerIndex ] );
|
||||
}
|
||||
|
||||
#elif defined( USE_VULKAN )
|
||||
if( vkcontext.queryIndex[ vkcontext.frameParity ] >= ( NUM_TIMESTAMP_QUERIES - 1 ) )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
VkCommandBuffer commandBuffer = vkcontext.commandBuffer[ vkcontext.frameParity ];
|
||||
VkQueryPool queryPool = vkcontext.queryPools[ vkcontext.frameParity ];
|
||||
|
||||
uint32 queryIndex = vkcontext.queryAssignedIndex[ vkcontext.frameParity ][ mainBlock * 2 + 0 ] = vkcontext.queryIndex[ vkcontext.frameParity ]++;
|
||||
vkCmdWriteTimestamp( commandBuffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, queryPool, queryIndex );
|
||||
|
||||
#elif defined(__APPLE__)
|
||||
// SRS - For OSX use elapsed time query for Apple OpenGL 4.1 using GL_TIME_ELAPSED vs GL_TIMESTAMP (which is not implemented on OSX)
|
||||
// SRS - OSX AMD drivers have a rendering bug (flashing colours) with an elasped time query when Shadow Mapping is on - turn off query for that case unless r_skipAMDWorkarounds is set
|
||||
if( !r_useShadowMapping.GetBool() || glConfig.vendor != VENDOR_AMD || r_skipAMDWorkarounds.GetBool() )
|
||||
{
|
||||
if( glcontext.renderLogMainBlockTimeQueryIds[ glcontext.frameParity ][ mainBlock * 2 + 1 ] == 0 )
|
||||
{
|
||||
glGenQueries( 1, &glcontext.renderLogMainBlockTimeQueryIds[ glcontext.frameParity ][ mainBlock * 2 + 1 ] );
|
||||
}
|
||||
|
||||
glBeginQuery( GL_TIME_ELAPSED_EXT, glcontext.renderLogMainBlockTimeQueryIds[ glcontext.frameParity ][ mainBlock * 2 + 1 ] );
|
||||
}
|
||||
|
||||
#else
|
||||
if( glcontext.renderLogMainBlockTimeQueryIds[ glcontext.frameParity ][ mainBlock * 2 ] == 0 )
|
||||
{
|
||||
glCreateQueries( GL_TIMESTAMP, 2, &glcontext.renderLogMainBlockTimeQueryIds[ glcontext.frameParity ][ mainBlock * 2 ] );
|
||||
}
|
||||
|
||||
glQueryCounter( glcontext.renderLogMainBlockTimeQueryIds[ glcontext.frameParity ][ mainBlock * 2 + 0 ], GL_TIMESTAMP );
|
||||
glcontext.renderLogMainBlockTimeQueryIssued[ glcontext.frameParity ][ mainBlock * 2 + 0 ]++;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -434,8 +184,6 @@ void idRenderLog::CloseMainBlock( int _block )
|
|||
block = renderLogMainBlock_t( _block );
|
||||
}
|
||||
|
||||
#if defined( USE_NVRHI )
|
||||
|
||||
int timerIndex = block + frameParity * MRB_TOTAL;
|
||||
|
||||
// SRS - Only issue a new end timer query if timer slot unused
|
||||
|
@ -444,32 +192,6 @@ void idRenderLog::CloseMainBlock( int _block )
|
|||
commandList->endTimerQuery( timerQueries[ timerIndex ] );
|
||||
timerUsed[ timerIndex ] = true;
|
||||
}
|
||||
|
||||
#elif defined( USE_VULKAN )
|
||||
if( vkcontext.queryIndex[ vkcontext.frameParity ] >= ( NUM_TIMESTAMP_QUERIES - 1 ) )
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
VkCommandBuffer commandBuffer = vkcontext.commandBuffer[ vkcontext.frameParity ];
|
||||
VkQueryPool queryPool = vkcontext.queryPools[ vkcontext.frameParity ];
|
||||
|
||||
uint32 queryIndex = vkcontext.queryAssignedIndex[ vkcontext.frameParity ][ block * 2 + 1 ] = vkcontext.queryIndex[ vkcontext.frameParity ]++;
|
||||
vkCmdWriteTimestamp( commandBuffer, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, queryPool, queryIndex );
|
||||
|
||||
#elif defined(__APPLE__)
|
||||
// SRS - For OSX use elapsed time query for Apple OpenGL 4.1 using GL_TIME_ELAPSED vs GL_TIMESTAMP (which is not implemented on OSX)
|
||||
// SRS - OSX AMD drivers have a rendering bug (flashing colours) with an elasped time query when Shadow Mapping is on - turn off query for that case unless r_skipAMDWorkarounds is set
|
||||
if( !r_useShadowMapping.GetBool() || glConfig.vendor != VENDOR_AMD || r_skipAMDWorkarounds.GetBool() )
|
||||
{
|
||||
glEndQuery( GL_TIME_ELAPSED_EXT );
|
||||
glcontext.renderLogMainBlockTimeQueryIssued[ glcontext.frameParity ][ block * 2 + 1 ]++;
|
||||
}
|
||||
|
||||
#else
|
||||
glQueryCounter( glcontext.renderLogMainBlockTimeQueryIds[ glcontext.frameParity ][ block * 2 + 1 ], GL_TIMESTAMP );
|
||||
glcontext.renderLogMainBlockTimeQueryIssued[ glcontext.frameParity ][ block * 2 + 1 ]++;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -483,8 +205,6 @@ void idRenderLog::FetchGPUTimers( backEndCounters_t& pc )
|
|||
frameCounter++;
|
||||
frameParity = ( frameParity + 1 ) % NUM_FRAME_DATA;
|
||||
|
||||
#if defined( USE_NVRHI )
|
||||
|
||||
for( int i = 0; i < MRB_TOTAL; i++ )
|
||||
{
|
||||
int timerIndex = i + frameParity * MRB_TOTAL;
|
||||
|
@ -535,7 +255,6 @@ void idRenderLog::FetchGPUTimers( backEndCounters_t& pc )
|
|||
// reset timer
|
||||
timerUsed[timerIndex] = false;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
|
@ -558,4 +277,3 @@ void idRenderLog::CloseBlock()
|
|||
{
|
||||
PC_EndNamedEvent( commandList );
|
||||
}
|
||||
// RB end
|
||||
|
|
|
@ -76,7 +76,6 @@ class idRenderLog
|
|||
private:
|
||||
renderLogMainBlock_t mainBlock;
|
||||
|
||||
#if defined( USE_NVRHI )
|
||||
nvrhi::CommandListHandle commandList;
|
||||
|
||||
uint64 frameCounter;
|
||||
|
@ -84,7 +83,6 @@ private:
|
|||
|
||||
idStaticList<nvrhi::TimerQueryHandle, MRB_TOTAL* NUM_FRAME_DATA> timerQueries;
|
||||
idStaticList<bool, MRB_TOTAL* NUM_FRAME_DATA> timerUsed;
|
||||
#endif
|
||||
|
||||
public:
|
||||
idRenderLog();
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
Doom 3 BFG Edition GPL Source Code
|
||||
Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company.
|
||||
Copyright (C) 2013-2020 Robert Beckebans
|
||||
Copyright (C) 2013-2023 Robert Beckebans
|
||||
Copyright (C) 2014-2016 Kot in Action Creative Artel
|
||||
|
||||
This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").
|
||||
|
@ -35,10 +35,8 @@ If you have questions concerning this license or the applicable additional terms
|
|||
#include "../framework/Common_local.h"
|
||||
#include "../imgui/BFGimgui.h"
|
||||
|
||||
#if defined( USE_NVRHI )
|
||||
#include <sys/DeviceManager.h>
|
||||
extern DeviceManager* deviceManager;
|
||||
#endif
|
||||
#include <sys/DeviceManager.h>
|
||||
extern DeviceManager* deviceManager;
|
||||
|
||||
|
||||
idRenderSystemLocal tr;
|
||||
|
@ -136,32 +134,7 @@ void idRenderSystemLocal::RenderCommandBuffers( const emptyCommand_t* const cmdH
|
|||
// draw 2D graphics
|
||||
if( !r_skipBackEnd.GetBool() )
|
||||
{
|
||||
#if defined(USE_NVRHI)
|
||||
|
||||
//renderLog.FetchGPUTimers( backend.pc );
|
||||
|
||||
// SRS - For OSX skip total rendering time query due to missing GL_TIMESTAMP support in Apple OpenGL 4.1, will calculate it inside SwapCommandBuffers_FinishRendering instead
|
||||
#elif !defined(USE_VULKAN) && !defined(__APPLE__)
|
||||
if( glConfig.timerQueryAvailable )
|
||||
{
|
||||
if( glcontext.renderLogMainBlockTimeQueryIds[ glcontext.frameParity ][ MRB_GPU_TIME ] == 0 )
|
||||
{
|
||||
glCreateQueries( GL_TIMESTAMP, 2, &glcontext.renderLogMainBlockTimeQueryIds[ glcontext.frameParity ][MRB_GPU_TIME ] );
|
||||
}
|
||||
|
||||
glQueryCounter( glcontext.renderLogMainBlockTimeQueryIds[ glcontext.frameParity ][ MRB_GPU_TIME * 2 + 0 ], GL_TIMESTAMP );
|
||||
backend.ExecuteBackEndCommands( cmdHead );
|
||||
glQueryCounter( glcontext.renderLogMainBlockTimeQueryIds[ glcontext.frameParity ][ MRB_GPU_TIME * 2 + 1 ], GL_TIMESTAMP );
|
||||
|
||||
glcontext.renderLogMainBlockTimeQueryIssued[ glcontext.frameParity ][ MRB_GPU_TIME * 2 + 1 ]++;
|
||||
|
||||
glFlush();
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
backend.ExecuteBackEndCommands( cmdHead );
|
||||
}
|
||||
backend.ExecuteBackEndCommands( cmdHead );
|
||||
}
|
||||
|
||||
// pass in null for now - we may need to do some map specific hackery in the future
|
||||
|
@ -677,11 +650,7 @@ void idRenderSystemLocal::SwapCommandBuffers_FinishRendering(
|
|||
|
||||
// keep capturing envprobes completely in the background
|
||||
// and only update the screen when we update the progress bar in the console
|
||||
#if defined( USE_NVRHI )
|
||||
if( !omitSwapBuffers )
|
||||
#else
|
||||
if( !takingEnvprobe )
|
||||
#endif
|
||||
{
|
||||
|
||||
#if !IMGUI_BFGUI
|
||||
|
@ -693,132 +662,10 @@ void idRenderSystemLocal::SwapCommandBuffers_FinishRendering(
|
|||
backend.GL_BlockingSwapBuffers();
|
||||
}
|
||||
|
||||
#if defined( USE_NVRHI ) || defined( USE_VULKAN )
|
||||
if( gpuMicroSec != NULL )
|
||||
{
|
||||
*gpuMicroSec = backend.pc.gpuMicroSec;
|
||||
}
|
||||
#else
|
||||
// read back the start and end timer queries from the previous frame
|
||||
if( glConfig.timerQueryAvailable )
|
||||
{
|
||||
GLuint64EXT gpuStartNanoseconds = 0;
|
||||
GLuint64EXT gpuEndNanoseconds = 0;
|
||||
|
||||
if( glcontext.renderLogMainBlockTimeQueryIssued[ glcontext.frameParity ^ 1 ][ MRB_GPU_TIME * 2 + 1 ] > 0 )
|
||||
{
|
||||
glGetQueryObjectui64vEXT( glcontext.renderLogMainBlockTimeQueryIds[ glcontext.frameParity ^ 1 ][ MRB_GPU_TIME * 2 + 0], GL_QUERY_RESULT, &gpuStartNanoseconds );
|
||||
glGetQueryObjectui64vEXT( glcontext.renderLogMainBlockTimeQueryIds[ glcontext.frameParity ^ 1 ][ MRB_GPU_TIME * 2 + 1], GL_QUERY_RESULT, &gpuEndNanoseconds );
|
||||
|
||||
backend.pc.gpuMicroSec = ( gpuEndNanoseconds - gpuStartNanoseconds ) / 1000;
|
||||
|
||||
if( gpuMicroSec != NULL )
|
||||
{
|
||||
*gpuMicroSec = backend.pc.gpuMicroSec;
|
||||
}
|
||||
}
|
||||
|
||||
if( glcontext.renderLogMainBlockTimeQueryIssued[ glcontext.frameParity ^ 1 ][ MRB_FILL_DEPTH_BUFFER * 2 + 1 ] > 0 )
|
||||
{
|
||||
glGetQueryObjectui64vEXT( glcontext.renderLogMainBlockTimeQueryIds[ glcontext.frameParity ^ 1 ][ MRB_FILL_DEPTH_BUFFER * 2 + 0], GL_QUERY_RESULT, &gpuStartNanoseconds );
|
||||
glGetQueryObjectui64vEXT( glcontext.renderLogMainBlockTimeQueryIds[ glcontext.frameParity ^ 1 ][ MRB_FILL_DEPTH_BUFFER * 2 + 1], GL_QUERY_RESULT, &gpuEndNanoseconds );
|
||||
|
||||
backend.pc.gpuDepthMicroSec = ( gpuEndNanoseconds - gpuStartNanoseconds ) / 1000;
|
||||
}
|
||||
|
||||
if( glcontext.renderLogMainBlockTimeQueryIssued[ glcontext.frameParity ^ 1 ][ MRB_SSAO_PASS * 2 + 1 ] > 0 )
|
||||
{
|
||||
glGetQueryObjectui64vEXT( glcontext.renderLogMainBlockTimeQueryIds[ glcontext.frameParity ^ 1 ][ MRB_SSAO_PASS * 2 + 0], GL_QUERY_RESULT, &gpuStartNanoseconds );
|
||||
glGetQueryObjectui64vEXT( glcontext.renderLogMainBlockTimeQueryIds[ glcontext.frameParity ^ 1 ][ MRB_SSAO_PASS * 2 + 1], GL_QUERY_RESULT, &gpuEndNanoseconds );
|
||||
|
||||
backend.pc.gpuScreenSpaceAmbientOcclusionMicroSec = ( gpuEndNanoseconds - gpuStartNanoseconds ) / 1000;
|
||||
}
|
||||
|
||||
if( glcontext.renderLogMainBlockTimeQueryIssued[ glcontext.frameParity ^ 1 ][ MRB_AMBIENT_PASS * 2 + 1 ] > 0 )
|
||||
{
|
||||
glGetQueryObjectui64vEXT( glcontext.renderLogMainBlockTimeQueryIds[ glcontext.frameParity ^ 1 ][ MRB_AMBIENT_PASS * 2 + 0], GL_QUERY_RESULT, &gpuStartNanoseconds );
|
||||
glGetQueryObjectui64vEXT( glcontext.renderLogMainBlockTimeQueryIds[ glcontext.frameParity ^ 1 ][ MRB_AMBIENT_PASS * 2 + 1], GL_QUERY_RESULT, &gpuEndNanoseconds );
|
||||
|
||||
backend.pc.gpuAmbientPassMicroSec = ( gpuEndNanoseconds - gpuStartNanoseconds ) / 1000;
|
||||
}
|
||||
|
||||
if( glcontext.renderLogMainBlockTimeQueryIssued[ glcontext.frameParity ^ 1 ][ MRB_DRAW_INTERACTIONS * 2 + 1 ] > 0 )
|
||||
{
|
||||
glGetQueryObjectui64vEXT( glcontext.renderLogMainBlockTimeQueryIds[ glcontext.frameParity ^ 1 ][ MRB_DRAW_INTERACTIONS * 2 + 0], GL_QUERY_RESULT, &gpuStartNanoseconds );
|
||||
glGetQueryObjectui64vEXT( glcontext.renderLogMainBlockTimeQueryIds[ glcontext.frameParity ^ 1 ][ MRB_DRAW_INTERACTIONS * 2 + 1], GL_QUERY_RESULT, &gpuEndNanoseconds );
|
||||
|
||||
backend.pc.gpuInteractionsMicroSec = ( gpuEndNanoseconds - gpuStartNanoseconds ) / 1000;
|
||||
}
|
||||
|
||||
if( glcontext.renderLogMainBlockTimeQueryIssued[ glcontext.frameParity ^ 1 ][ MRB_DRAW_SHADER_PASSES * 2 + 1 ] > 0 )
|
||||
{
|
||||
glGetQueryObjectui64vEXT( glcontext.renderLogMainBlockTimeQueryIds[ glcontext.frameParity ^ 1 ][ MRB_DRAW_SHADER_PASSES * 2 + 0], GL_QUERY_RESULT, &gpuStartNanoseconds );
|
||||
glGetQueryObjectui64vEXT( glcontext.renderLogMainBlockTimeQueryIds[ glcontext.frameParity ^ 1 ][ MRB_DRAW_SHADER_PASSES * 2 + 1], GL_QUERY_RESULT, &gpuEndNanoseconds );
|
||||
|
||||
backend.pc.gpuShaderPassMicroSec = ( gpuEndNanoseconds - gpuStartNanoseconds ) / 1000;
|
||||
}
|
||||
|
||||
if( glcontext.renderLogMainBlockTimeQueryIssued[ glcontext.frameParity ^ 1 ][ MRB_POSTPROCESS * 2 + 1 ] > 0 )
|
||||
{
|
||||
glGetQueryObjectui64vEXT( glcontext.renderLogMainBlockTimeQueryIds[ glcontext.frameParity ^ 1 ][ MRB_POSTPROCESS * 2 + 0], GL_QUERY_RESULT, &gpuStartNanoseconds );
|
||||
glGetQueryObjectui64vEXT( glcontext.renderLogMainBlockTimeQueryIds[ glcontext.frameParity ^ 1 ][ MRB_POSTPROCESS * 2 + 1], GL_QUERY_RESULT, &gpuEndNanoseconds );
|
||||
|
||||
backend.pc.gpuPostProcessingMicroSec = ( gpuEndNanoseconds - gpuStartNanoseconds ) / 1000;
|
||||
}
|
||||
|
||||
// SRS - For OSX OpenGL calculate total rendering time vs direct measurement due to missing GL_TIMESTAMP support in Apple OpenGL 4.1
|
||||
#if defined(__APPLE__)
|
||||
// SRS - On OSX gpuMicroSec starts with only a portion of the GPU rendering time, must add additional components for more accurate estimate
|
||||
backend.pc.gpuMicroSec += backend.pc.gpuDepthMicroSec + backend.pc.gpuScreenSpaceAmbientOcclusionMicroSec + backend.pc.gpuScreenSpaceReflectionsMicroSec + backend.pc.gpuAmbientPassMicroSec + backend.pc.gpuInteractionsMicroSec + backend.pc.gpuShaderPassMicroSec + backend.pc.gpuPostProcessingMicroSec;
|
||||
|
||||
// SRS - For OSX elapsed time queries, no need to perform unnecessary calls to glGetQueryObjectui64vEXT since gpuStartNanoseconds will always equal 0
|
||||
if( glcontext.renderLogMainBlockTimeQueryIssued[ glcontext.frameParity ^ 1 ][ MRB_FILL_GEOMETRY_BUFFER * 2 + 1 ] > 0 )
|
||||
{
|
||||
glGetQueryObjectui64vEXT( glcontext.renderLogMainBlockTimeQueryIds[ glcontext.frameParity ^ 1 ][ MRB_FILL_GEOMETRY_BUFFER * 2 + 1], GL_QUERY_RESULT, &gpuEndNanoseconds );
|
||||
|
||||
backend.pc.gpuMicroSec += gpuEndNanoseconds / 1000;
|
||||
}
|
||||
|
||||
if( glcontext.renderLogMainBlockTimeQueryIssued[ glcontext.frameParity ^ 1 ][ MRB_FOG_ALL_LIGHTS * 2 + 1 ] > 0 )
|
||||
{
|
||||
glGetQueryObjectui64vEXT( glcontext.renderLogMainBlockTimeQueryIds[ glcontext.frameParity ^ 1 ][ MRB_FOG_ALL_LIGHTS * 2 + 1], GL_QUERY_RESULT, &gpuEndNanoseconds );
|
||||
|
||||
backend.pc.gpuMicroSec += gpuEndNanoseconds / 1000;
|
||||
}
|
||||
|
||||
if( glcontext.renderLogMainBlockTimeQueryIssued[ glcontext.frameParity ^ 1 ][ MRB_DRAW_SHADER_PASSES_POST * 2 + 1 ] > 0 )
|
||||
{
|
||||
glGetQueryObjectui64vEXT( glcontext.renderLogMainBlockTimeQueryIds[ glcontext.frameParity ^ 1 ][ MRB_DRAW_SHADER_PASSES_POST * 2 + 1], GL_QUERY_RESULT, &gpuEndNanoseconds );
|
||||
|
||||
backend.pc.gpuMicroSec += gpuEndNanoseconds / 1000;
|
||||
}
|
||||
|
||||
if( glcontext.renderLogMainBlockTimeQueryIssued[ glcontext.frameParity ^ 1 ][ MRB_DRAW_DEBUG_TOOLS * 2 + 1 ] > 0 )
|
||||
{
|
||||
glGetQueryObjectui64vEXT( glcontext.renderLogMainBlockTimeQueryIds[ glcontext.frameParity ^ 1 ][ MRB_DRAW_DEBUG_TOOLS * 2 + 1], GL_QUERY_RESULT, &gpuEndNanoseconds );
|
||||
|
||||
backend.pc.gpuMicroSec += gpuEndNanoseconds / 1000;
|
||||
}
|
||||
|
||||
if( glcontext.renderLogMainBlockTimeQueryIssued[ glcontext.frameParity ^ 1 ][ MRB_DRAW_GUI * 2 + 1 ] > 0 )
|
||||
{
|
||||
glGetQueryObjectui64vEXT( glcontext.renderLogMainBlockTimeQueryIds[ glcontext.frameParity ^ 1 ][ MRB_DRAW_GUI * 2 + 1], GL_QUERY_RESULT, &gpuEndNanoseconds );
|
||||
|
||||
backend.pc.gpuMicroSec += gpuEndNanoseconds / 1000;
|
||||
}
|
||||
|
||||
if( gpuMicroSec != NULL )
|
||||
{
|
||||
*gpuMicroSec = backend.pc.gpuMicroSec;
|
||||
}
|
||||
#endif
|
||||
|
||||
for( int i = 0; i < MRB_TOTAL_QUERIES; i++ )
|
||||
{
|
||||
glcontext.renderLogMainBlockTimeQueryIssued[ glcontext.frameParity ^ 1 ][ i ] = 0;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
//------------------------------
|
||||
|
||||
|
@ -857,7 +704,6 @@ void idRenderSystemLocal::SwapCommandBuffers_FinishRendering(
|
|||
|
||||
// RB: resize HDR buffers
|
||||
Framebuffer::CheckFramebuffers();
|
||||
// RB end
|
||||
|
||||
// check for errors
|
||||
GL_CheckErrors();
|
||||
|
@ -952,18 +798,10 @@ const emptyCommand_t* idRenderSystemLocal::SwapCommandBuffers_FinishCommandBuffe
|
|||
// set the time for shader effects in 2D rendering
|
||||
frameShaderTime = Sys_Milliseconds() * 0.001;
|
||||
|
||||
#if 1 //!defined(USE_VULKAN)
|
||||
// RB: TODO RC_SET_BUFFER is not handled in OpenGL
|
||||
setBufferCommand_t* cmd2 = ( setBufferCommand_t* )R_GetCommandBuffer( sizeof( *cmd2 ) );
|
||||
cmd2->commandId = RC_SET_BUFFER;
|
||||
|
||||
#if defined(USE_VULKAN) || defined(USE_NVRHI)
|
||||
cmd2->buffer = 0;
|
||||
#else
|
||||
cmd2->buffer = ( int )GL_BACK;
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
// the old command buffer can now be rendered, while the new one can
|
||||
// be built in parallel
|
||||
|
|
|
@ -145,9 +145,6 @@ idCVar r_skipDynamicTextures( "r_skipDynamicTextures", "0", CVAR_RENDERER | CVAR
|
|||
idCVar r_skipCopyTexture( "r_skipCopyTexture", "0", CVAR_RENDERER | CVAR_BOOL, "do all rendering, but don't actually copyTexSubImage2D" );
|
||||
idCVar r_skipBackEnd( "r_skipBackEnd", "0", CVAR_RENDERER | CVAR_BOOL, "don't draw anything" );
|
||||
idCVar r_skipRender( "r_skipRender", "0", CVAR_RENDERER | CVAR_BOOL, "skip 3D rendering, but pass 2D" );
|
||||
// RB begin
|
||||
idCVar r_skipRenderContext( "r_skipRenderContext", "0", CVAR_RENDERER | CVAR_BOOL, "DISABLED: NULL the rendering context during backend 3D rendering" );
|
||||
// RB end
|
||||
idCVar r_skipTranslucent( "r_skipTranslucent", "0", CVAR_RENDERER | CVAR_BOOL, "skip the translucent interaction rendering" );
|
||||
idCVar r_skipAmbient( "r_skipAmbient", "0", CVAR_RENDERER | CVAR_BOOL, "bypasses all non-interaction drawing" );
|
||||
idCVar r_skipNewAmbient( "r_skipNewAmbient", "0", CVAR_RENDERER | CVAR_BOOL | CVAR_ARCHIVE, "bypasses all vertex/fragment program ambient drawing" );
|
||||
|
|
Loading…
Reference in a new issue