/****************************************************************************** * The MIT License (MIT) * * Copyright (c) 2019-2024 Baldur Karlsson * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. ******************************************************************************/ #pragma once // This file controls the telemetry/analytics functionality in RenderDoc. // // If you don't care about any details and just want to make sure this is turned off for your builds // then go below to #define RENDERDOC_ANALYTICS_ENABLE and change it to 0. That will cease all // recording and reporting of analytics data. It won't delete any previously recorded analytics and // won't stop any other builds from working, since the code that knows about what the analytics is // will be compiled out. // // ------------------------------------------------------------------------------------------------- // // RenderDoc's analytics works in two phases: Data is recorded first to an internal database that // can contain more information than will be sent, to allow for accurate tracking before // aggregation. This isn't as scary as it sounds - e.g. consider the 'UsageLevel' stat that's // reported, which is a number in the range 1 to 31 indicating how many days in the month the // program was launched. In order to track that, we need to have a sticky flag for each day to count // it, since given a number "20" it's impossible to know if we've counted today or not. // // Similar cases are found for averages that need to store the number of cases and a total, which is // then flattened down into just an average for reporting. // // This database is stored internally and then converted to the report in the 'AnalyticsSerialise' // function. // // Once the report is sent, the database is reset and begins the next period. // this is the root switch that can turn off *all* analytics code globally #define RENDERDOC_ANALYTICS_ENABLE 1 // we don't want any of this to be accessible to script, only code. #if !defined(SWIG) && !defined(SWIG_GENERATED) // We also compile out all of the code if analytics are disabled so there's not even a code // reference to where the data is collected. #if RENDERDOC_ANALYTICS_ENABLE struct AnalyticsAverage { double Total = 0.0; int Count = 0; void Add(double Val) { Total += Val; Count++; } AnalyticsAverage() = default; AnalyticsAverage(QVariant v) { QVariantMap vmap = v.toMap(); Total = vmap[lit("Total")].toDouble(); Count = vmap[lit("Count")].toInt(); } QVariant toVariant(bool reporting) const { if(reporting) { if(Count == 0) return 0.0; return Total / double(Count); } QVariantMap ret; ret[lit("Total")] = Total; ret[lit("Count")] = Count; return ret; } }; class PersistantConfig; struct ICaptureContext; // we set this struct to byte-packing, so that any change will affect sizeof() and fail a // static_assert in AnalyticsSerialise. This only has to be on one platform since it will block // building when a commit is pushed. This struct isn't memcpy'd around and isn't performance // sensitive, so there's no need to have perfectly aligned data. #if defined(_MSC_VER) #pragma pack(push, 1) #endif // we declare the analystics data struct here, this contains the information we're storing // If you add anything to this struct, make sure to update AnalyticsSerialise, and // AnalyticsDocumentation. struct Analytics { // utility function - loads the analytics from disk and initialise the Analytics::db member. static void Load(); // utility function - explicitly disables the analytics and sets it into a black-hole mode that // does nothing. static void Disable(); // utility function - performs any UI-level prompting, such as asking the user if they want to // opt-out, or manually vetting a report for uploading. static void Prompt(ICaptureContext &ctx, PersistantConfig &config); // the singleton instance of analytics. May be NULL if analytics aren't initialised or have been // opted-out from. static Analytics *db; // utility function - displays an annotated report documenting what each member means static void DocumentReport(); // Function to save the analytics to disk, if it's been initialised. Every set macro below will // call this after the data is set to flush it to disk. void Save(); // book-keeping: contains the year/month where this database was started. Thus if current date != // saved date, then we should try to submit the analytics report. struct { int Year = 0; int Month = 0; } Date; // version number. Most data is opportunistically gathered, so if some data is missing then it // just doesn't get included in the report so we can add new data, but we bump the version // here if something more significant changes. int Version = 1; struct { // The version string (MAJOR_MINOR_VERSION_STRING) of this build. QString RenderDocVersion; // The distribution information (DISTRIBUTION_NAME) for this build. QString DistributionVersion; // A human readable name of the operating system. QString OSVersion; // Either 32 or 64 indicating which bit-depth the UI is running as int Bitness = 0; // whether a development build has been run - either a nightly build or a local build. bool DevelBuildRun = false; // whether an official build has been run - whether distributed from the RenderDoc website or // through a linux distribution bool OfficialBuildRun = false; // Counts which unique days in the last month the program was run bool DaysUsed[32] = {0}; } Metadata; struct { // how long do captures take to load, on average AnalyticsAverage LoadTime; } Performance; // which APIs have been used QStringList APIs; // A list of which GPU vendors have been used for replay QStringList GPUVendors; // which UI features have been used, as a simple yes/no struct { bool Bookmarks = false; bool ResourceInspect = false; bool ShaderEditing = false; bool CallstackResolve = false; bool PixelHistory = false; bool DrawcallTimes = false; bool PerformanceCounters = false; bool PythonInterop = false; bool CustomTextureVisualise = false; bool ImageViewer = false; bool CaptureComments = false; bool AndroidRemoteReplay = false; bool NonAndroidRemoteReplay = false; } UIFeatures; struct { bool EventBrowser = false; bool PipelineState = false; bool MeshOutput = false; bool RawBuffer = false; bool Texture = false; bool Shader = false; } Export; struct { bool Vertex = false; bool Pixel = false; bool Compute = false; } ShaderDebug; struct { bool Drawcall = false; bool Wireframe = false; bool Depth = false; bool Stencil = false; bool BackfaceCull = false; bool ViewportScissor = false; bool NaN = false; bool Clipping = false; bool ClearBeforePass = false; bool ClearBeforeDraw = false; bool QuadOverdrawPass = false; bool QuadOverdrawDraw = false; bool TriangleSizePass = false; bool TriangleSizeDraw = false; } TextureOverlays; // If some particular API specific features are seen in a capture, as a simple yes/no. See // APIProperties struct { bool ShaderLinkage = false; bool YUVTextures = false; bool SparseResources = false; bool MultiGPU = false; bool D3D12Bundle = false; bool DXILShaders = false; } CaptureFeatures; }; #if defined(_MSC_VER) #pragma pack(pop) #endif // straightforward set of a value #define ANALYTIC_SET(name, val) \ do \ { \ if(Analytics::db) \ { \ auto value = val; \ if(Analytics::db->name != value) \ { \ Analytics::db->name = value; \ Analytics::db->Save(); \ } \ } \ } while(0); // add a data point to an average #define ANALYTIC_ADDAVG(name, val) \ do \ { \ if(Analytics::db) \ { \ Analytics::db->name.Add(val); \ Analytics::db->Save(); \ } \ } while(0); // add an element to an array, if it's not already present #define ANALYTIC_ADDUNIQ(name, val) \ do \ { \ if(Analytics::db) \ { \ bool found = false; \ for(auto &v : Analytics::db->name) \ { \ if(v == val) \ { \ found = true; \ break; \ } \ } \ \ if(!found) \ { \ Analytics::db->name.push_back(val); \ Analytics::db->Save(); \ } \ } \ } while(0); #else // here we declare macro stubs that satisfy the use in the codebase. #define ANALYTIC_SET(name, val) (void)val #define ANALYTIC_ADDAVG(name, val) (void)val #define ANALYTIC_ADDUNIQ(name, val) (void)val class PersistantConfig; struct ICaptureContext; namespace Analytics { void Disable(); void Load(); void Prompt(ICaptureContext &ctx, PersistantConfig &config); void DocumentReport(); }; #endif #endif