mirror of
https://github.com/lubosz/overte.git
synced 2025-04-14 03:06:20 +02:00
Merge pull request #12850 from SimonWalton-HiFi/early-trace
Enable Interface tracing from earliest execution point
This commit is contained in:
commit
3ac4a1db2d
13 changed files with 109 additions and 40 deletions
|
@ -490,7 +490,7 @@ public:
|
|||
// Don't actually crash in debug builds, in case this apparent deadlock is simply from
|
||||
// the developer actively debugging code
|
||||
#ifdef NDEBUG
|
||||
deadlockDetectionCrash();
|
||||
deadlockDetectionCrash();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
@ -773,7 +773,6 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) {
|
|||
steamClient->init();
|
||||
}
|
||||
|
||||
DependencyManager::set<tracing::Tracer>();
|
||||
PROFILE_SET_THREAD_NAME("Main Thread");
|
||||
|
||||
#if defined(Q_OS_WIN)
|
||||
|
@ -958,10 +957,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
|||
auto steamClient = PluginManager::getInstance()->getSteamClientPlugin();
|
||||
setProperty(hifi::properties::STEAM, (steamClient && steamClient->isRunning()));
|
||||
setProperty(hifi::properties::CRASHED, _previousSessionCrashed);
|
||||
|
||||
{
|
||||
const QString TEST_SCRIPT = "--testScript";
|
||||
const QString TRACE_FILE = "--traceFile";
|
||||
const QStringList args = arguments();
|
||||
for (int i = 0; i < args.size() - 1; ++i) {
|
||||
if (args.at(i) == TEST_SCRIPT) {
|
||||
|
@ -969,10 +966,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
|||
if (QFileInfo(testScriptPath).exists()) {
|
||||
setProperty(hifi::properties::TEST, QUrl::fromLocalFile(testScriptPath));
|
||||
}
|
||||
} else if (args.at(i) == TRACE_FILE) {
|
||||
QString traceFilePath = args.at(i + 1);
|
||||
setProperty(hifi::properties::TRACING, traceFilePath);
|
||||
DependencyManager::get<tracing::Tracer>()->startTracing();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1018,6 +1011,10 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
|||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
nodeList->startThread();
|
||||
|
||||
const char** constArgv = const_cast<const char**>(argv);
|
||||
if (cmdOptionExists(argc, constArgv, "--disableWatchdog")) {
|
||||
DISABLE_WATCHDOG = true;
|
||||
}
|
||||
// Set up a watchdog thread to intentionally crash the application on deadlocks
|
||||
if (!DISABLE_WATCHDOG) {
|
||||
(new DeadlockWatchdogThread())->start();
|
||||
|
@ -1227,7 +1224,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
|||
connect(&_entityEditSender, &EntityEditPacketSender::packetSent, this, &Application::packetSent);
|
||||
connect(&_entityEditSender, &EntityEditPacketSender::addingEntityWithCertificate, this, &Application::addingEntityWithCertificate);
|
||||
|
||||
const char** constArgv = const_cast<const char**>(argv);
|
||||
QString concurrentDownloadsStr = getCmdOption(argc, constArgv, "--concurrent-downloads");
|
||||
bool success;
|
||||
int concurrentDownloads = concurrentDownloadsStr.toInt(&success);
|
||||
|
|
|
@ -31,6 +31,8 @@
|
|||
#include "UserActivityLogger.h"
|
||||
#include "MainWindow.h"
|
||||
|
||||
#include "Profile.h"
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
extern "C" {
|
||||
typedef int(__stdcall * CHECKMINSPECPROC) ();
|
||||
|
@ -40,6 +42,26 @@ extern "C" {
|
|||
int main(int argc, const char* argv[]) {
|
||||
setupHifiApplication(BuildInfo::INTERFACE_NAME);
|
||||
|
||||
// Early check for --traceFile argument
|
||||
auto tracer = DependencyManager::set<tracing::Tracer>();
|
||||
const char * traceFile = nullptr;
|
||||
const QString traceFileFlag("--traceFile");
|
||||
float traceDuration = 0.0f;
|
||||
for (int a = 1; a < argc; ++a) {
|
||||
if (traceFileFlag == argv[a] && argc > a + 1) {
|
||||
traceFile = argv[a + 1];
|
||||
if (argc > a + 2) {
|
||||
traceDuration = atof(argv[a + 2]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (traceFile != nullptr) {
|
||||
tracer->startTracing();
|
||||
}
|
||||
|
||||
PROFILE_SYNC_BEGIN(startup, "main startup", "");
|
||||
|
||||
#ifdef Q_OS_LINUX
|
||||
QApplication::setAttribute(Qt::AA_DontUseNativeMenuBar);
|
||||
#endif
|
||||
|
@ -235,7 +257,18 @@ int main(int argc, const char* argv[]) {
|
|||
argvExtended.push_back("--ignore-gpu-blacklist");
|
||||
int argcExtended = (int)argvExtended.size();
|
||||
|
||||
PROFILE_SYNC_END(startup, "main startup", "");
|
||||
PROFILE_SYNC_BEGIN(startup, "app full ctor", "");
|
||||
Application app(argcExtended, const_cast<char**>(argvExtended.data()), startupTime, runningMarkerExisted);
|
||||
PROFILE_SYNC_END(startup, "app full ctor", "");
|
||||
|
||||
|
||||
QTimer exitTimer;
|
||||
if (traceDuration > 0.0f) {
|
||||
exitTimer.setSingleShot(true);
|
||||
QObject::connect(&exitTimer, &QTimer::timeout, &app, &Application::quit);
|
||||
exitTimer.start(int(1000 * traceDuration));
|
||||
}
|
||||
|
||||
#if 0
|
||||
// If we failed the OpenGLVersion check, log it.
|
||||
|
@ -273,6 +306,11 @@ int main(int argc, const char* argv[]) {
|
|||
qCDebug(interfaceapp, "Created QT Application.");
|
||||
exitCode = app.exec();
|
||||
server.close();
|
||||
|
||||
if (traceFile != nullptr) {
|
||||
tracer->stopTracing();
|
||||
tracer->serialize(QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation) + "/" + traceFile);
|
||||
}
|
||||
}
|
||||
|
||||
Application::shutdownPlugins();
|
||||
|
|
|
@ -129,6 +129,7 @@ void Context::executeFrame(const FramePointer& frame) const {
|
|||
}
|
||||
|
||||
bool Context::makeProgram(Shader& shader, const Shader::BindingSet& bindings, const Shader::CompilationHandler& handler) {
|
||||
PROFILE_RANGE_EX(app, "makeProgram", 0xff4040c0, shader.getID());
|
||||
// If we're running in another DLL context, we need to fetch the program callback out of the application
|
||||
// FIXME find a way to do this without reliance on Qt app properties
|
||||
if (!_makeProgramCallback) {
|
||||
|
|
|
@ -75,6 +75,7 @@ Shader::Pointer Shader::createGeometry(const Source& source) {
|
|||
}
|
||||
|
||||
ShaderPointer Shader::createOrReuseProgramShader(Type type, const Pointer& vertexShader, const Pointer& geometryShader, const Pointer& pixelShader) {
|
||||
PROFILE_RANGE(app, "createOrReuseProgramShader");
|
||||
ProgramMapKey key(0);
|
||||
|
||||
if (vertexShader && vertexShader->getType() == VERTEX) {
|
||||
|
|
|
@ -27,6 +27,8 @@
|
|||
#include "impl/SharedObject.h"
|
||||
#include "impl/TextureCache.h"
|
||||
|
||||
#include "Profile.h"
|
||||
|
||||
using namespace hifi::qml;
|
||||
using namespace hifi::qml::impl;
|
||||
|
||||
|
@ -284,6 +286,7 @@ void OffscreenSurface::loadInternal(const QUrl& qmlSource,
|
|||
bool createNewContext,
|
||||
QQuickItem* parent,
|
||||
const QmlContextObjectCallback& callback) {
|
||||
PROFILE_RANGE_EX(app, "OffscreenSurface::loadInternal", 0xffff00ff, 0, { std::make_pair("url", qmlSource.toDisplayString()) });
|
||||
if (QThread::currentThread() != thread()) {
|
||||
qFatal("Called load on a non-surface thread");
|
||||
}
|
||||
|
@ -304,7 +307,11 @@ void OffscreenSurface::loadInternal(const QUrl& qmlSource,
|
|||
}
|
||||
|
||||
auto targetContext = contextForUrl(finalQmlSource, parent, createNewContext);
|
||||
auto qmlComponent = new QQmlComponent(getSurfaceContext()->engine(), finalQmlSource, QQmlComponent::PreferSynchronous);
|
||||
QQmlComponent* qmlComponent;
|
||||
{
|
||||
PROFILE_RANGE(app, "new QQmlComponent");
|
||||
qmlComponent = new QQmlComponent(getSurfaceContext()->engine(), finalQmlSource, QQmlComponent::PreferSynchronous);
|
||||
}
|
||||
if (qmlComponent->isLoading()) {
|
||||
connect(qmlComponent, &QQmlComponent::statusChanged, this,
|
||||
[=](QQmlComponent::Status) { finishQmlLoad(qmlComponent, targetContext, parent, callback); });
|
||||
|
@ -318,6 +325,7 @@ void OffscreenSurface::finishQmlLoad(QQmlComponent* qmlComponent,
|
|||
QQmlContext* qmlContext,
|
||||
QQuickItem* parent,
|
||||
const QmlContextObjectCallback& callback) {
|
||||
PROFILE_RANGE(app, "finishQmlLoad");
|
||||
disconnect(qmlComponent, &QQmlComponent::statusChanged, this, 0);
|
||||
if (qmlComponent->isError()) {
|
||||
for (const auto& error : qmlComponent->errors()) {
|
||||
|
|
|
@ -105,7 +105,10 @@ void SharedObject::create(OffscreenSurface* surface) {
|
|||
|
||||
// Create a QML engine.
|
||||
auto qmlEngine = acquireEngine(surface);
|
||||
_qmlContext = new QQmlContext(qmlEngine->rootContext(), qmlEngine);
|
||||
{
|
||||
PROFILE_RANGE(startup, "new QQmlContext");
|
||||
_qmlContext = new QQmlContext(qmlEngine->rootContext(), qmlEngine);
|
||||
}
|
||||
surface->onRootContextCreated(_qmlContext);
|
||||
emit surface->rootContextCreated(_qmlContext);
|
||||
|
||||
|
@ -175,6 +178,7 @@ static size_t globalEngineRefCount{ 0 };
|
|||
#endif
|
||||
|
||||
QQmlEngine* SharedObject::acquireEngine(OffscreenSurface* surface) {
|
||||
PROFILE_RANGE(startup, "acquireEngine");
|
||||
Q_ASSERT(QThread::currentThread() == qApp->thread());
|
||||
|
||||
QQmlEngine* result = nullptr;
|
||||
|
|
|
@ -36,7 +36,7 @@ public:
|
|||
}
|
||||
};
|
||||
|
||||
Engine::Engine() : Task("Engine", EngineTask::JobModel::create()),
|
||||
Engine::Engine() : Task(EngineTask::JobModel::create("Engine")),
|
||||
_renderContext(std::make_shared<RenderContext>())
|
||||
{
|
||||
}
|
||||
|
|
|
@ -134,9 +134,12 @@ void ShapePlumber::addPipeline(const Filter& filter, const gpu::ShaderPointer& p
|
|||
locations->lightClusterFrustumBufferUnit = -1;
|
||||
}
|
||||
|
||||
auto gpuPipeline = gpu::Pipeline::create(program, state);
|
||||
auto shapePipeline = std::make_shared<Pipeline>(gpuPipeline, locations, batchSetter, itemSetter);
|
||||
addPipelineHelper(filter, key, 0, shapePipeline);
|
||||
{
|
||||
PROFILE_RANGE(app, "Pipeline::create");
|
||||
auto gpuPipeline = gpu::Pipeline::create(program, state);
|
||||
auto shapePipeline = std::make_shared<Pipeline>(gpuPipeline, locations, batchSetter, itemSetter);
|
||||
addPipelineHelper(filter, key, 0, shapePipeline);
|
||||
}
|
||||
}
|
||||
|
||||
const ShapePipelinePointer ShapePlumber::pickPipeline(RenderArgs* args, const Key& key) const {
|
||||
|
|
|
@ -27,6 +27,7 @@ Q_LOGGING_CATEGORY(trace_simulation_animation, "trace.simulation.animation")
|
|||
Q_LOGGING_CATEGORY(trace_simulation_animation_detail, "trace.simulation.animation.detail")
|
||||
Q_LOGGING_CATEGORY(trace_simulation_physics, "trace.simulation.physics")
|
||||
Q_LOGGING_CATEGORY(trace_simulation_physics_detail, "trace.simulation.physics.detail")
|
||||
Q_LOGGING_CATEGORY(trace_startup, "trace.startup")
|
||||
Q_LOGGING_CATEGORY(trace_workload, "trace.workload")
|
||||
|
||||
#if defined(NSIGHT_FOUND)
|
||||
|
|
|
@ -32,6 +32,7 @@ Q_DECLARE_LOGGING_CATEGORY(trace_simulation_animation)
|
|||
Q_DECLARE_LOGGING_CATEGORY(trace_simulation_animation_detail)
|
||||
Q_DECLARE_LOGGING_CATEGORY(trace_simulation_physics)
|
||||
Q_DECLARE_LOGGING_CATEGORY(trace_simulation_physics_detail)
|
||||
Q_DECLARE_LOGGING_CATEGORY(trace_startup)
|
||||
Q_DECLARE_LOGGING_CATEGORY(trace_workload)
|
||||
|
||||
class Duration {
|
||||
|
|
|
@ -47,7 +47,7 @@ protected:
|
|||
bool _doAbortTask{ false };
|
||||
};
|
||||
|
||||
// JobContext class is the base calss for the context object which is passed through all the Job::run calls thoughout the graph of jobs
|
||||
// JobContext class is the base class for the context object which is passed through all the Job::run calls thoughout the graph of jobs
|
||||
// It is used to communicate to the job::run its context and various state information the job relies on.
|
||||
// It specifically provide access to:
|
||||
// - The taskFlow object allowing for messaging control flow commands from within a Job::run
|
||||
|
@ -73,19 +73,21 @@ class JobConcept {
|
|||
public:
|
||||
using Config = JobConfig;
|
||||
|
||||
JobConcept(QConfigPointer config) : _config(config) {}
|
||||
JobConcept(const std::string& name, QConfigPointer config) : _config(config), _name(name) {}
|
||||
virtual ~JobConcept() = default;
|
||||
|
||||
const std::string& getName() const { return _name; }
|
||||
|
||||
virtual const Varying getInput() const { return Varying(); }
|
||||
virtual const Varying getOutput() const { return Varying(); }
|
||||
|
||||
virtual QConfigPointer& getConfiguration() { return _config; }
|
||||
virtual void applyConfiguration() = 0;
|
||||
|
||||
void setCPURunTime(double mstime) { std::static_pointer_cast<Config>(_config)->setCPURunTime(mstime); }
|
||||
|
||||
QConfigPointer _config;
|
||||
protected:
|
||||
const std::string _name;
|
||||
};
|
||||
|
||||
|
||||
|
@ -122,7 +124,7 @@ public:
|
|||
|
||||
class Concept : public JobConcept {
|
||||
public:
|
||||
Concept(QConfigPointer config) : JobConcept(config) {}
|
||||
Concept(const std::string& name, QConfigPointer config) : JobConcept(name, config) {}
|
||||
virtual ~Concept() = default;
|
||||
|
||||
virtual void run(const ContextPointer& jobContext) = 0;
|
||||
|
@ -143,8 +145,8 @@ public:
|
|||
const Varying getOutput() const override { return _output; }
|
||||
|
||||
template <class... A>
|
||||
Model(const Varying& input, QConfigPointer config, A&&... args) :
|
||||
Concept(config),
|
||||
Model(const std::string& name, const Varying& input, QConfigPointer config, A&&... args) :
|
||||
Concept(name, config),
|
||||
_data(Data(std::forward<A>(args)...)),
|
||||
_input(input),
|
||||
_output(Output()) {
|
||||
|
@ -152,12 +154,14 @@ public:
|
|||
}
|
||||
|
||||
template <class... A>
|
||||
static std::shared_ptr<Model> create(const Varying& input, A&&... args) {
|
||||
return std::make_shared<Model>(input, std::make_shared<C>(), std::forward<A>(args)...);
|
||||
static std::shared_ptr<Model> create(const std::string& name, const Varying& input, A&&... args) {
|
||||
return std::make_shared<Model>(name, input, std::make_shared<C>(), std::forward<A>(args)...);
|
||||
}
|
||||
|
||||
|
||||
void applyConfiguration() override {
|
||||
Duration profileRange(trace_render(), ("configure::" + JobConcept::getName()).c_str());
|
||||
|
||||
jobConfigure(_data, *std::static_pointer_cast<C>(Concept::_config));
|
||||
}
|
||||
|
||||
|
@ -173,8 +177,9 @@ public:
|
|||
template <class T, class O, class C = Config> using ModelO = Model<T, C, None, O>;
|
||||
template <class T, class I, class O, class C = Config> using ModelIO = Model<T, C, I, O>;
|
||||
|
||||
Job(std::string name, ConceptPointer concept) : _concept(concept), _name(name) {}
|
||||
Job(ConceptPointer concept) : _concept(concept) {}
|
||||
|
||||
const std::string& getName() const { return _concept->getName(); }
|
||||
const Varying getInput() const { return _concept->getInput(); }
|
||||
const Varying getOutput() const { return _concept->getOutput(); }
|
||||
QConfigPointer& getConfiguration() const { return _concept->getConfiguration(); }
|
||||
|
@ -193,9 +198,9 @@ public:
|
|||
}
|
||||
|
||||
virtual void run(const ContextPointer& jobContext) {
|
||||
PerformanceTimer perfTimer(_name.c_str());
|
||||
PerformanceTimer perfTimer(getName().c_str());
|
||||
// NOTE: rather than use the PROFILE_RANGE macro, we create a Duration manually
|
||||
Duration profileRange(jobContext->profileCategory, _name.c_str());
|
||||
Duration profileRange(jobContext->profileCategory, ("run::" + getName()).c_str());
|
||||
auto start = usecTimestampNow();
|
||||
|
||||
_concept->run(jobContext);
|
||||
|
@ -203,11 +208,8 @@ public:
|
|||
_concept->setCPURunTime((double)(usecTimestampNow() - start) / 1000.0);
|
||||
}
|
||||
|
||||
const std::string& getName() const { return _name; }
|
||||
|
||||
protected:
|
||||
ConceptPointer _concept;
|
||||
std::string _name = "";
|
||||
};
|
||||
|
||||
|
||||
|
@ -230,7 +232,7 @@ public:
|
|||
using ConceptPointer = typename JobType::ConceptPointer;
|
||||
using Jobs = std::vector<JobType>;
|
||||
|
||||
Task(std::string name, ConceptPointer concept) : JobType(name, concept) {}
|
||||
Task(ConceptPointer concept) : JobType(concept) {}
|
||||
|
||||
class TaskConcept : public Concept {
|
||||
public:
|
||||
|
@ -259,11 +261,11 @@ public:
|
|||
return jobIt;
|
||||
}
|
||||
|
||||
TaskConcept(const Varying& input, QConfigPointer config) : Concept(config), _input(input) {}
|
||||
TaskConcept(const std::string& name, const Varying& input, QConfigPointer config) : Concept(name, config), _input(input) {}
|
||||
|
||||
// Create a new job in the container's queue; returns the job's output
|
||||
template <class NT, class... NA> const Varying addJob(std::string name, const Varying& input, NA&&... args) {
|
||||
_jobs.emplace_back(name, (NT::JobModel::create(input, std::forward<NA>(args)...)));
|
||||
_jobs.emplace_back((NT::JobModel::create(name, input, std::forward<NA>(args)...)));
|
||||
|
||||
// Conect the child config to this task's config
|
||||
std::static_pointer_cast<TaskConfig>(Concept::getConfiguration())->connectChildConfig(_jobs.back().getConfiguration(), name);
|
||||
|
@ -284,16 +286,18 @@ public:
|
|||
|
||||
Data _data;
|
||||
|
||||
TaskModel(const Varying& input, QConfigPointer config) :
|
||||
TaskConcept(input, config),
|
||||
TaskModel(const std::string& name, const Varying& input, QConfigPointer config) :
|
||||
TaskConcept(name, input, config),
|
||||
_data(Data()) {}
|
||||
|
||||
template <class... A>
|
||||
static std::shared_ptr<TaskModel> create(const Varying& input, A&&... args) {
|
||||
auto model = std::make_shared<TaskModel>(input, std::make_shared<C>());
|
||||
|
||||
model->_data.build(*(model), model->_input, model->_output, std::forward<A>(args)...);
|
||||
static std::shared_ptr<TaskModel> create(const std::string& name, const Varying& input, A&&... args) {
|
||||
auto model = std::make_shared<TaskModel>(name, input, std::make_shared<C>());
|
||||
|
||||
{
|
||||
Duration profileRange(trace_render(), ("build::" + model->getName()).c_str());
|
||||
model->_data.build(*(model), model->_input, model->_output, std::forward<A>(args)...);
|
||||
}
|
||||
// Recreate the Config to use the templated type
|
||||
model->createConfiguration();
|
||||
model->applyConfiguration();
|
||||
|
@ -302,9 +306,9 @@ public:
|
|||
}
|
||||
|
||||
template <class... A>
|
||||
static std::shared_ptr<TaskModel> create(A&&... args) {
|
||||
static std::shared_ptr<TaskModel> create(const std::string& name, A&&... args) {
|
||||
const auto input = Varying(Input());
|
||||
return create(input, std::forward<A>(args)...);
|
||||
return create(name, input, std::forward<A>(args)...);
|
||||
}
|
||||
|
||||
void createConfiguration() {
|
||||
|
@ -326,6 +330,7 @@ public:
|
|||
}
|
||||
|
||||
void applyConfiguration() override {
|
||||
Duration profileRange(trace_render(), ("configure::" + JobConcept::getName()).c_str());
|
||||
jobConfigure(_data, *std::static_pointer_cast<C>(Concept::_config));
|
||||
for (auto& job : TaskConcept::_jobs) {
|
||||
job.applyConfiguration();
|
||||
|
|
|
@ -256,6 +256,15 @@ void OffscreenQmlSurface::initializeEngine(QQmlEngine* engine) {
|
|||
#if !defined(Q_OS_ANDROID)
|
||||
rootContext->setContextProperty("FileTypeProfile", new FileTypeProfile(rootContext));
|
||||
rootContext->setContextProperty("HFWebEngineProfile", new HFWebEngineProfile(rootContext));
|
||||
{
|
||||
PROFILE_RANGE(startup, "FileTypeProfile");
|
||||
rootContext->setContextProperty("FileTypeProfile", new FileTypeProfile(rootContext));
|
||||
}
|
||||
{
|
||||
PROFILE_RANGE(startup, "HFWebEngineProfile");
|
||||
rootContext->setContextProperty("HFWebEngineProfile", new HFWebEngineProfile(rootContext));
|
||||
|
||||
}
|
||||
#endif
|
||||
rootContext->setContextProperty("Paths", DependencyManager::get<PathUtils>().data());
|
||||
rootContext->setContextProperty("Tablet", DependencyManager::get<TabletScriptingInterface>().data());
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
|
||||
#include <PathUtils.h>
|
||||
#include "OffscreenQmlSurface.h"
|
||||
#include "Profile.h"
|
||||
|
||||
OffscreenQmlSurfaceCache::OffscreenQmlSurfaceCache() {
|
||||
}
|
||||
|
@ -38,6 +39,7 @@ void OffscreenQmlSurfaceCache::reserve(const QString& rootSource, int count) {
|
|||
}
|
||||
|
||||
void OffscreenQmlSurfaceCache::release(const QString& rootSource, const QSharedPointer<OffscreenQmlSurface>& surface) {
|
||||
PROFILE_RANGE(app, "buildSurface");
|
||||
surface->pause();
|
||||
_cache[rootSource].push_back(surface);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue