mirror of
https://github.com/copyrighttxt/watrbx-game-engine.git
synced 2026-09-04 20:57:49 +00:00
1869 lines
62 KiB
C++
1869 lines
62 KiB
C++
#include "stdafx.h"
|
|
#include "RenderView.h"
|
|
|
|
#include "Util/FileSystem.h"
|
|
#include "g3d/GImage.h"
|
|
#include "g3d/BinaryOutput.h"
|
|
#include "V8DataModel/Lighting.h"
|
|
#include "V8DataModel/Decal.h"
|
|
#include "V8DataModel/PartInstance.h"
|
|
#include "V8DataModel/Workspace.h"
|
|
#include "V8DataModel/Sky.h"
|
|
#include "V8DataModel/ContentProvider.h"
|
|
#include "V8DataModel/MeshContentProvider.h"
|
|
#include "V8DataModel/Camera.h"
|
|
#include "V8DataModel/TextService.h"
|
|
#include "V8DataModel/RenderHooksService.h"
|
|
#include "V8DataModel/Stats.h"
|
|
#include "v8datamodel/DataModel.h"
|
|
#if defined(RBX_PLATFORM_DURANGO)
|
|
# include "v8datamodel/PlatformService.h"
|
|
#endif
|
|
#include "v8world/World.h"
|
|
#include "v8datamodel/UserInputService.h"
|
|
|
|
#include "util/standardout.h"
|
|
#include "rbx/MathUtil.h"
|
|
#include "VisualEngine.h"
|
|
#include "VertexStreamer.h"
|
|
#include "TypesetterBitmap.h"
|
|
#include "Water.h"
|
|
|
|
#include "SceneManager.h"
|
|
#include "SceneUpdater.h"
|
|
|
|
#include "TextureCompositor.h"
|
|
|
|
#include "GfxBase/RenderStats.h"
|
|
#include "GfxBase/FrameRateManager.h"
|
|
#include "util/Profiling.h"
|
|
#include "util/IMetric.h"
|
|
#include "util/RobloxGoogleAnalytics.h"
|
|
|
|
#include "TextureManager.h"
|
|
#include "AdornRender.h"
|
|
#include "LightGrid.h"
|
|
#include "MaterialGenerator.h"
|
|
#include "GeometryGenerator.h"
|
|
|
|
#if !defined(RBX_PLATFORM_DURANGO)
|
|
#include "ObjectExporter.h"
|
|
#endif
|
|
#include "TextureAtlas.h"
|
|
|
|
#include "FastLog.h"
|
|
|
|
#include "Sky.h"
|
|
#include "Util.h"
|
|
#include "rbx/SystemUtil.h"
|
|
|
|
#include "GfxCore/Framebuffer.h"
|
|
#include "GfxCore/Device.h"
|
|
|
|
#include "SpatialHashedScene.h"
|
|
#include "CullableSceneNode.h"
|
|
#include "LightObject.h"
|
|
|
|
#include "rbx/rbxTime.h"
|
|
|
|
#include "rbx/Profiler.h"
|
|
#include "GfxCore/States.h"
|
|
#include "ShaderManager.h"
|
|
|
|
#include "G3D/Quat.h"
|
|
|
|
#include "GfxBase/AdornBillboarder.h"
|
|
|
|
#if defined(_WIN32) && !defined(RBX_PLATFORM_DURANGO)
|
|
#include <mmsystem.h>
|
|
#endif
|
|
|
|
LOGGROUP(ThumbnailRender)
|
|
LOGGROUP(RenderBreakdown)
|
|
LOGGROUP(RenderStatsOnLogs)
|
|
LOGGROUP(Graphics)
|
|
|
|
FASTFLAGVARIABLE(DebugAdornsDisabled, false)
|
|
|
|
DYNAMIC_FASTFLAGVARIABLE(ScreenShotDuplicationFix, false)
|
|
|
|
FASTINTVARIABLE(OutlineBrightnessMin, 50)
|
|
FASTINTVARIABLE(OutlineBrightnessMax, 160)
|
|
FASTINTVARIABLE(OutlineThickness, 40)
|
|
FASTFLAGVARIABLE(RenderThumbModelReflectionsFix,false);
|
|
|
|
FASTINTVARIABLE(RenderShadowIntensity, 75)
|
|
|
|
FASTFLAG(UseDynamicTypesetterUTF8)
|
|
FASTFLAG(FramerateVisualizerShow)
|
|
FASTFLAG(TaskSchedulerCyclicExecutive)
|
|
|
|
// Test: 0 - no throttling, 1 - adds 5ms to frame time, 2 - adds 10ms to frame time
|
|
ABTEST_NEWUSERS_VARIABLE(FrameRateThrottling)
|
|
FASTFLAG(ClientABTestingEnabled)
|
|
|
|
FASTFLAG(TypesettersReleaseResources);
|
|
|
|
FASTFLAGVARIABLE(RenderFixFog, false)
|
|
|
|
FASTFLAGVARIABLE(RenderVR, false)
|
|
|
|
FASTFLAGVARIABLE(RenderLowLatencyLoop, false)
|
|
|
|
FASTFLAGVARIABLE(RenderUIAs3DInVR, true)
|
|
|
|
// Studs-to-meters ratio
|
|
const float kVRScale = 3.f;
|
|
|
|
// Canvas size for 2D UI in VR
|
|
const float kVRUICanvas = 720;
|
|
|
|
// Depth of 2D UI in studs
|
|
const float kVRUIDepth = 3;
|
|
|
|
// Field of view of 2D UI in degrees
|
|
const float kVRUIFOV = 50;
|
|
|
|
#ifdef _WIN32
|
|
extern "C"
|
|
{
|
|
_declspec(dllexport) int NvOptimusEnablement = 1;
|
|
_declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1;
|
|
}
|
|
#endif
|
|
|
|
|
|
namespace RBX
|
|
{
|
|
|
|
void RenderView_InitModule()
|
|
{
|
|
class RenderViewFactory : public IViewBaseFactory
|
|
{
|
|
public:
|
|
RenderViewFactory()
|
|
{
|
|
ViewBase::RegisterFactory(CRenderSettings::Direct3D9, this);
|
|
ViewBase::RegisterFactory(CRenderSettings::Direct3D11, this);
|
|
ViewBase::RegisterFactory(CRenderSettings::OpenGL, this);
|
|
}
|
|
|
|
ViewBase* Create(CRenderSettings::GraphicsMode mode, OSContext* context, CRenderSettings* renderSettings)
|
|
{
|
|
return new Graphics::RenderView(mode, context, renderSettings);
|
|
}
|
|
};
|
|
|
|
static RenderViewFactory factory;
|
|
}
|
|
|
|
void RenderView_ShutdownModule()
|
|
{
|
|
}
|
|
|
|
}
|
|
|
|
namespace RBX
|
|
{
|
|
namespace Graphics
|
|
{
|
|
double busyWaitLoop(double ms);
|
|
|
|
class FrameRateManagerStatsItem : public RBX::Stats::Item
|
|
{
|
|
int qualityLevel;
|
|
bool autoQuality;
|
|
int numberOfSettles;
|
|
double averageSwitches;
|
|
double averageFPS;
|
|
|
|
public:
|
|
FrameRateManagerStatsItem()
|
|
: qualityLevel(0)
|
|
, autoQuality(false)
|
|
, numberOfSettles(0)
|
|
, averageSwitches(0)
|
|
, averageFPS(0)
|
|
{
|
|
}
|
|
|
|
static shared_ptr<FrameRateManagerStatsItem> create()
|
|
{
|
|
shared_ptr<FrameRateManagerStatsItem> result = RBX::Creatable<RBX::Instance>::create<FrameRateManagerStatsItem>();
|
|
|
|
result->createBoundChildItem("QualityLevel", result->qualityLevel);
|
|
result->createBoundChildItem("AutoQuality", result->autoQuality);
|
|
result->createBoundChildItem("NumberOfSettles", result->numberOfSettles);
|
|
result->createBoundChildItem("AverageSwitches", result->averageSwitches);
|
|
result->createBoundChildItem("AverageFPS", result->averageFPS);
|
|
|
|
return result;
|
|
}
|
|
|
|
void updateData(FrameRateManager* frm)
|
|
{
|
|
RBX::FrameRateManager::Metrics metrics = frm->GetMetrics();
|
|
|
|
qualityLevel = metrics.QualityLevel;
|
|
autoQuality = metrics.AutoQuality;
|
|
numberOfSettles = metrics.NumberOfSettles;
|
|
averageSwitches = metrics.AverageSwitchesPerSettle;
|
|
averageFPS = metrics.AverageFps;
|
|
}
|
|
};
|
|
|
|
struct GraphicsModeTranslation
|
|
{
|
|
CRenderSettings::GraphicsMode settingsItem;
|
|
Device::API api;
|
|
};
|
|
|
|
static const unsigned validGraphicsModes = 3;
|
|
static const GraphicsModeTranslation gGraphicsModesTranslation[validGraphicsModes] =
|
|
{
|
|
{ CRenderSettings::Direct3D9, Device::API_Direct3D9 },
|
|
{ CRenderSettings::Direct3D11, Device::API_Direct3D11},
|
|
{ CRenderSettings::OpenGL, Device::API_OpenGL}
|
|
};
|
|
|
|
static CoordinateFrame computeVRFrame(const DeviceVR::Pose& pose)
|
|
{
|
|
CoordinateFrame result;
|
|
result.translation = Vector3(pose.position) * kVRScale;
|
|
result.rotation = G3D::Quat(pose.orientation).toRotationMatrix();
|
|
|
|
return result;
|
|
}
|
|
|
|
static Vector2 computeCanvasSize(Device* device)
|
|
{
|
|
if (device->getVR())
|
|
{
|
|
return Vector2(kVRUICanvas, kVRUICanvas);
|
|
}
|
|
else if (Framebuffer* framebuffer = device->getMainFramebuffer())
|
|
{
|
|
int viewScale = (device->getCaps().retina) ? 2 : 1;
|
|
|
|
return Vector2(framebuffer->getWidth(), framebuffer->getHeight()) / viewScale;
|
|
}
|
|
else
|
|
{
|
|
return Vector2();
|
|
}
|
|
}
|
|
|
|
RenderView::RenderView(CRenderSettings::GraphicsMode graphicsMode, OSContext* context, CRenderSettings* renderSettings)
|
|
: deltaMs(0)
|
|
, timestampMs(0)
|
|
, prepareTime(0)
|
|
, totalRenderTime(0)
|
|
, prepareAverage(15)
|
|
, performAverage(15)
|
|
, presentAverage(15)
|
|
, artificialDelay(0)
|
|
, gpuAverage(15)
|
|
, lightingValid(false)
|
|
, doScreenshot(false)
|
|
, adornsEnabled(true)
|
|
, outlinesEnabled(true)
|
|
, fogEndCurrentFRM(10000)
|
|
, fogEndTargetFRM(10000)
|
|
, frameDataCallback(FrameDataCallback())
|
|
{
|
|
FASTLOG1(FLog::ViewRbxInit, "RenderView created - %p", this);
|
|
|
|
#if defined(_WIN32) && !defined(RBX_PLATFORM_DURANGO)
|
|
timeBeginPeriod(1);
|
|
#endif
|
|
|
|
bool translatioFound = false;
|
|
for (unsigned i = 0; i < validGraphicsModes; ++i)
|
|
{
|
|
if (gGraphicsModesTranslation[i].settingsItem == graphicsMode)
|
|
{
|
|
device.reset(Device::create(gGraphicsModesTranslation[i].api, context->hWnd));
|
|
translatioFound = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!translatioFound)
|
|
throw RBX::runtime_error("Not supported graphics mode");
|
|
|
|
const DeviceCaps& caps = device->getCaps();
|
|
|
|
if (!caps.supportsFramebuffer)
|
|
throw std::runtime_error("Device does not support framebuffers");
|
|
|
|
if (!caps.supportsShaders && !caps.supportsFFP)
|
|
throw std::runtime_error("Device does not support shaders or FFP");
|
|
|
|
visualEngine.reset(new VisualEngine(device.get(), renderSettings));
|
|
}
|
|
|
|
void RenderView::enableAdorns(bool enable)
|
|
{
|
|
adornsEnabled = enable;
|
|
}
|
|
|
|
void RenderView::initResources()
|
|
{
|
|
FASTLOG1(FLog::ViewRbxInit, "RenderView::initResources - %p", this);
|
|
|
|
FASTLOG(FLog::ViewRbxInit, "RenderView::initResources finish");
|
|
}
|
|
|
|
void RenderView::bindWorkspace(boost::shared_ptr<RBX::DataModel> dataModel)
|
|
{
|
|
if (this->dataModel == dataModel)
|
|
return;
|
|
|
|
FASTLOG2(FLog::ViewRbxInit, "BindWorkspace, new datamodel %p, old datamodel: %p", dataModel.get(), this->dataModel.get());
|
|
|
|
this->lightingValid = false;
|
|
this->doScreenshot = false;
|
|
|
|
if (this->dataModel)
|
|
{
|
|
if (frameRateManagerStatsItem)
|
|
{
|
|
frameRateManagerStatsItem->setParent(NULL);
|
|
frameRateManagerStatsItem.reset();
|
|
}
|
|
|
|
lightingChangedConnection.disconnect();
|
|
screenshotConnection.disconnect();
|
|
|
|
if(TextService* textService = ServiceProvider::create<TextService>(this->dataModel.get()))
|
|
{
|
|
textService->clearTypesetters();
|
|
}
|
|
|
|
visualEngine->bindWorkspace(shared_ptr<DataModel>());
|
|
}
|
|
|
|
this->dataModel = dataModel;
|
|
|
|
if (this->dataModel)
|
|
{
|
|
visualEngine->bindWorkspace(dataModel);
|
|
|
|
Lighting* lighting = ServiceProvider::create<Lighting>(dataModel.get());
|
|
lightingChangedConnection = lighting->lightingChangedSignal.connect(boost::bind(&RenderView::invalidateLighting, this, _1));
|
|
screenshotConnection = this->dataModel->screenshotSignal.connect(boost::bind(&RenderView::onTakeScreenshot, this));
|
|
|
|
if (TextService* textService = ServiceProvider::create<TextService>(dataModel.get()))
|
|
{
|
|
for (Text::Font font = Text::FONT_LEGACY; font != Text::FONT_LAST; font=Text::Font(font+1))
|
|
{
|
|
if (FFlag::TypesettersReleaseResources)
|
|
{
|
|
visualEngine->getTypesetter(font)->loadResources(visualEngine->getTextureManager(), visualEngine->getGlyphAtlas());
|
|
}
|
|
textService->registerTypesetter(TextService::FromTextFont(font), visualEngine->getTypesetter(font));
|
|
}
|
|
}
|
|
|
|
FrameRateManager* frm = visualEngine->getFrameRateManager();
|
|
if (frm)
|
|
{
|
|
frm->StartCapturingMetrics();
|
|
|
|
RBX::Stats::StatsService* stats = RBX::ServiceProvider::create<RBX::Stats::StatsService>(this->dataModel.get());
|
|
|
|
frameRateManagerStatsItem = FrameRateManagerStatsItem::create();
|
|
frameRateManagerStatsItem->setName("FrameRateManager");
|
|
frameRateManagerStatsItem->setParent(stats);
|
|
frameRateManagerStatsItem->updateData(frm);
|
|
}
|
|
|
|
if (visualEngine->getDevice()->getMainFramebuffer())
|
|
{
|
|
this->dataModel->setInitialScreenSize(computeCanvasSize(visualEngine->getDevice()));
|
|
}
|
|
|
|
RBX::RenderHooksService* service = RBX::ServiceProvider::find<RBX::RenderHooksService>(dataModel.get());
|
|
service->setRenderHooks(this);
|
|
|
|
service->reloadShadersSignal.connect(boost::bind(&RenderView::reloadShaders, this));
|
|
|
|
// default fog
|
|
fogEndCurrentFRM = fogEndTargetFRM = lighting->getFogEnd();
|
|
|
|
visualEngine->getSettings()->setDrawConnectors(dataModel->isStudio());
|
|
}
|
|
}
|
|
|
|
|
|
RenderView::~RenderView(void)
|
|
{
|
|
bindWorkspace(boost::shared_ptr<RBX::DataModel>());
|
|
|
|
sendFeatureLevelStats();
|
|
|
|
#if defined(_WIN32) && !defined(RBX_PLATFORM_DURANGO)
|
|
timeEndPeriod(1);
|
|
#endif
|
|
FASTLOG(FLog::ViewRbxInit, "RenderView destroyed");
|
|
}
|
|
|
|
void RenderView::sendFeatureLevelStats()
|
|
{
|
|
if (visualEngine.get() && visualEngine.get()->getDevice())
|
|
{
|
|
std::string osAndFeatureLvl = SystemUtil::osPlatform() + " " + visualEngine.get()->getDevice()->getFeatureLevel();
|
|
RBX::RobloxGoogleAnalytics::trackEvent(GA_CATEGORY_GAME, "GraphicsFeatureLevel", osAndFeatureLvl.c_str());
|
|
}
|
|
}
|
|
|
|
void RenderView::onResize(int cx, int cy)
|
|
{
|
|
}
|
|
|
|
FrameRateManager* RenderView::getFrameRateManager()
|
|
{
|
|
return visualEngine->getFrameRateManager();
|
|
}
|
|
|
|
RBX::Instance* RenderView::getWorkspace()
|
|
{
|
|
return dataModel->getWorkspace();
|
|
}
|
|
|
|
void RenderView::invalidateLighting(bool updateSkybox)
|
|
{
|
|
lightingValid = false;
|
|
}
|
|
|
|
void RenderView::onTakeScreenshot()
|
|
{
|
|
doScreenshot = true;
|
|
}
|
|
|
|
bool RenderView::getAndClearDoScreenshot()
|
|
{
|
|
bool result = doScreenshot;
|
|
doScreenshot = false;
|
|
return result;
|
|
}
|
|
|
|
void RenderView::updateFog()
|
|
{
|
|
static const float TAU = 0.04f; // dampening factor as in: exp( -x * TAU )
|
|
|
|
RBX::Lighting* lighting = ServiceProvider::create<RBX::Lighting>(dataModel.get());
|
|
|
|
if (FFlag::RenderFixFog)
|
|
{
|
|
float currDeltaFRM = fogEndTargetFRM - fogEndCurrentFRM;
|
|
if (currDeltaFRM > 0)
|
|
fogEndCurrentFRM += currDeltaFRM * TAU;
|
|
else
|
|
fogEndCurrentFRM = fogEndTargetFRM;
|
|
|
|
float fogEnd = std::max(fogEndCurrentFRM, lighting->getFogEnd()*0.5f);
|
|
float fogStart = G3D::clamp(lighting->getFogStart(), 0.0f, fogEnd - 0.1f);
|
|
|
|
visualEngine->getSceneManager()->setFog(lighting->getFogColor(), fogStart, fogEnd);
|
|
}
|
|
else
|
|
{
|
|
float frmEnd = this->fogEndTargetFRM;
|
|
float fogStart = lighting->getFogStart();
|
|
float fogEnd = lighting->getFogEnd();
|
|
float range = fogEnd - fogStart;
|
|
|
|
// set fog
|
|
fogEnd = std::min(fogEnd, frmEnd);
|
|
float currDelta = fogEnd - this->fogEndCurrentFRM;
|
|
fogEnd = this->fogEndCurrentFRM += currDelta * TAU;
|
|
if (frmEnd < lighting->getFogEnd() && currDelta < 0)
|
|
{
|
|
fogEnd = this->fogEndCurrentFRM = frmEnd; // force instant transition
|
|
}
|
|
|
|
fogEnd = this->fogEndCurrentFRM = std::max(fogEnd, lighting->getFogEnd()*0.5f);
|
|
fogStart = fogEnd - range * (fogEnd / lighting->getFogEnd());
|
|
|
|
// Make sure fog range is not empty to avoid issues
|
|
fogStart = G3D::clamp(fogStart, 0.0f, fogEnd - 0.1f);
|
|
|
|
visualEngine->getSceneManager()->setFog(lighting->getFogColor(), fogStart, fogEnd);
|
|
}
|
|
}
|
|
|
|
void RenderView::updateLighting(Lighting* lighting)
|
|
{
|
|
SceneManager* smgr = visualEngine->getSceneManager();
|
|
|
|
if (!lightingValid)
|
|
{
|
|
// Sky
|
|
if (FFlag::RenderThumbModelReflectionsFix || !lighting->isSkySuppressed()) // prepare the sky regardless of whether it's suppressed, because it's needed for the envmaps
|
|
{
|
|
Sky* sky = smgr->getSky();
|
|
|
|
if (lighting->sky)
|
|
{
|
|
sky->setSkyBox(lighting->sky->skyRt, lighting->sky->skyLf, lighting->sky->skyBk, lighting->sky->skyFt, lighting->sky->skyUp, lighting->sky->skyDn);
|
|
sky->update(lighting->getSkyParameters(), lighting->sky->getNumStars(), lighting->sky->drawCelestialBodies);
|
|
}
|
|
else
|
|
{
|
|
sky->setSkyBoxDefault();
|
|
sky->update(lighting->getSkyParameters(), 3000, true);
|
|
}
|
|
|
|
// Update clear color for time-of-day
|
|
lighting->setClearColor(lighting->getSkyParameters().skyAmbient);
|
|
}
|
|
|
|
// Lighting
|
|
presetLighting(lighting);
|
|
|
|
lightingValid = true;
|
|
}
|
|
|
|
smgr->setSkyEnabled(!lighting->isSkySuppressed());
|
|
smgr->setClearColor(Color4(lighting->getClearColor(), lighting->getClearAlpha()));
|
|
}
|
|
|
|
static void reportVRUsage(int placeId)
|
|
{
|
|
std::string placeIdStr = format("%d", placeId);
|
|
|
|
RobloxGoogleAnalytics::trackEventWithoutThrottling(GA_CATEGORY_GAME, "VR", placeIdStr.c_str());
|
|
}
|
|
|
|
void RenderView::updateVR()
|
|
{
|
|
if (!FFlag::RenderVR)
|
|
return;
|
|
|
|
RBXPROFILER_SCOPE("Render", "updateVR");
|
|
|
|
if (DeviceVR* vr = visualEngine->getDevice()->getVR())
|
|
{
|
|
// GA
|
|
if (dataModel && dataModel->getPlaceID())
|
|
{
|
|
static boost::once_flag flag = BOOST_ONCE_INIT;
|
|
boost::call_once(flag, boost::bind(&reportVRUsage, dataModel->getPlaceID()));
|
|
}
|
|
|
|
// Disable throttling for VR (ideally render job should control this...)
|
|
TaskScheduler::singleton().DataModel30fpsThrottle = false;
|
|
|
|
// Put FRM into aggressive mode
|
|
visualEngine->getFrameRateManager()->setAggressivePerformance(true);
|
|
|
|
// Read VR sensor data
|
|
vr->update();
|
|
|
|
DeviceVR::State vrState = vr->getState();
|
|
|
|
// Setup interaction between DataModel and game
|
|
if (UserInputService* uis = ServiceProvider::find<UserInputService>(dataModel.get()))
|
|
{
|
|
RBXPROFILER_SCOPE("Render", "DM");
|
|
|
|
uis->setVREnabled(true);
|
|
|
|
if (vrState.headPose.valid)
|
|
{
|
|
CoordinateFrame cframe = computeVRFrame(vrState.headPose);
|
|
|
|
// Legacy, to be removed
|
|
uis->setUserHeadCFrame(cframe);
|
|
|
|
uis->setUserCFrame(UserInputService::USERCFRAME_HEAD, cframe);
|
|
}
|
|
|
|
if (vrState.handPose[0].valid)
|
|
{
|
|
CoordinateFrame cframe = computeVRFrame(vrState.handPose[0]);
|
|
|
|
uis->setUserCFrame(UserInputService::USERCFRAME_LEFTHAND, cframe);
|
|
}
|
|
|
|
if (vrState.handPose[1].valid)
|
|
{
|
|
CoordinateFrame cframe = computeVRFrame(vrState.handPose[1]);
|
|
|
|
uis->setUserCFrame(UserInputService::USERCFRAME_RIGHTHAND, cframe);
|
|
}
|
|
|
|
if (uis->checkAndClearRecenterUserHeadCFrameRequest())
|
|
vr->recenter();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Reenable throttling if necessary
|
|
TaskScheduler::singleton().DataModel30fpsThrottle = DataModel::throttleAt30Fps;
|
|
|
|
// Put FRM into non-aggressive mode
|
|
visualEngine->getFrameRateManager()->setAggressivePerformance(false);
|
|
|
|
// Clean up VR state
|
|
if (UserInputService* uis = ServiceProvider::find<UserInputService>(dataModel.get()))
|
|
{
|
|
uis->setVREnabled(false);
|
|
uis->setUserHeadCFrame(CoordinateFrame());
|
|
}
|
|
}
|
|
}
|
|
|
|
void RenderView::enableVR(bool enabled)
|
|
{
|
|
if (!FFlag::RenderVR)
|
|
return;
|
|
|
|
visualEngine->getDevice()->setVR(enabled);
|
|
}
|
|
|
|
const char* RenderView::getVRDeviceName()
|
|
{
|
|
return device->getVR() ? "VR" : 0;
|
|
}
|
|
|
|
double RenderView::getMetricValue(const std::string& name)
|
|
{
|
|
FrameRateManager* frm = visualEngine->getFrameRateManager();
|
|
|
|
if (name == "Delta Between Renders")
|
|
{
|
|
return frm != NULL ? frm->GetFrameTimeAverage() : 0.0;
|
|
}
|
|
else if (name == "Total Render")
|
|
{
|
|
return frm != NULL ? frm->GetRenderTimeAverage() : 0.0;
|
|
}
|
|
else if (name == "Present Time")
|
|
{
|
|
return presentAverage.getStats().average;
|
|
}
|
|
else if (name == "GPU Delay")
|
|
{
|
|
return gpuAverage.getStats().average;
|
|
}
|
|
|
|
return -1.0;
|
|
}
|
|
|
|
std::string RenderView::getRenderStatsMetric(const std::string& name)
|
|
{
|
|
if (name.compare(0, 15, "RenderStatsPass") == 0)
|
|
{
|
|
RenderPassStats* stats =
|
|
(name == "RenderStatsPassScene") ? &visualEngine->getRenderStats()->passScene :
|
|
(name == "RenderStatsPassShadow") ? &visualEngine->getRenderStats()->passShadow :
|
|
(name == "RenderStatsPassUI") ? &visualEngine->getRenderStats()->passUI :
|
|
(name == "RenderStatsPass3dAdorns") ? &visualEngine->getRenderStats()->pass3DAdorns :
|
|
(name == "RenderStatsPassTotal") ? &visualEngine->getRenderStats()->passTotal :
|
|
NULL;
|
|
|
|
if (!stats) return "unknown pass";
|
|
|
|
return RBX::format("%d (%df %dv %dp %ds)",
|
|
stats->batches, stats->faces, stats->vertices, stats->passChanges, stats->stateChanges);
|
|
}
|
|
|
|
if (name == "RenderStatsResolution")
|
|
{
|
|
return RBX::format("%u x %u", visualEngine->getViewWidth(), visualEngine->getViewHeight());
|
|
}
|
|
|
|
if (name == "RenderStatsTimeTotal")
|
|
{
|
|
FrameRateManager* frm = visualEngine->getFrameRateManager();
|
|
double totalWork = frm->GetRenderTimeAverage();
|
|
|
|
double prepareAve = frm->GetPrepareTimeAverage();
|
|
double performAve = performAverage.getStats().average;
|
|
double presentAve = presentAverage.getStats().average;
|
|
|
|
return RBX::format("%.1f ms (prepare %.1f, perform %.1f, present %.1f, bonus %.1f)",
|
|
totalWork,
|
|
prepareAve,
|
|
performAve,
|
|
presentAve,
|
|
artificialDelay);
|
|
}
|
|
|
|
if (name == "RenderStatsDelta")
|
|
{
|
|
FrameRateManager* frm = visualEngine->getFrameRateManager();
|
|
double totalWork = frm->GetRenderTimeAverage();
|
|
|
|
double prepareAve = frm->GetPrepareTimeAverage();
|
|
double performAve = performAverage.getStats().average;
|
|
double presentAve = presentAverage.getStats().average;
|
|
double workAve = prepareAve + performAve + presentAve;
|
|
|
|
double frameTime = frm->GetFrameTimeAverage();
|
|
|
|
return RBX::format("%.1f ms (work %.1f, marshal %.1f, idle %.1f)",
|
|
frameTime,
|
|
workAve,
|
|
workAve > totalWork ? 0.0 : totalWork - workAve,
|
|
totalWork > frameTime ? 0.0 : frameTime - totalWork);
|
|
}
|
|
|
|
if (name == "RenderStatsGeometryGen")
|
|
{
|
|
RBX::RenderStats* stats = visualEngine->getRenderStats();
|
|
SceneUpdater* su = visualEngine->getSceneUpdater();
|
|
|
|
return RBX::format("fast %dc %dp mega %dc queue %dc",
|
|
stats->lastFrameFast.clusters, stats->lastFrameFast.parts,
|
|
stats->lastFrameMegaClusterChunks,
|
|
int(su->getUpdateQueueSize()));
|
|
}
|
|
|
|
if (name == "RenderStatsClusters")
|
|
{
|
|
RBX::RenderStats* stats = visualEngine->getRenderStats();
|
|
|
|
return RBX::format("fw %dc %dp; dyn %dc %dp; hum %dc %dp",
|
|
stats->clusterFastFW.clusters, stats->clusterFastFW.parts,
|
|
stats->clusterFast.clusters, stats->clusterFast.parts,
|
|
stats->clusterFastHumanoid.clusters, stats->clusterFastHumanoid.parts);
|
|
}
|
|
|
|
if (name == "RenderStatsFRMConfig")
|
|
{
|
|
FrameRateManager* frm = visualEngine->getFrameRateManager();
|
|
|
|
return RBX::format("level %d (auto %s)",
|
|
frm->GetQualityLevel(), visualEngine->getSettings()->getQualityLevel() == CRenderSettings::QualityAuto ? "on" : "off");
|
|
}
|
|
|
|
if (name == "RenderStatsFRMBlocks")
|
|
{
|
|
FrameRateManager* frm = visualEngine->getFrameRateManager();
|
|
|
|
return RBX::format("%d (target %d)", (int)frm->GetVisibleBlockCounter(), (int)frm->GetVisibleBlockTarget());
|
|
}
|
|
|
|
if (name == "RenderStatsFRMDistance")
|
|
{
|
|
FrameRateManager* frm = visualEngine->getFrameRateManager();
|
|
|
|
return RBX::format("render %d view %d (...%d)", (int)sqrt(frm->GetRenderCullSqDistance()), (int)sqrt(frm->GetViewCullSqDistance()), frm->GetRecomputeDistanceDelay());
|
|
}
|
|
|
|
if (name == "RenderStatsFRMAdjust")
|
|
{
|
|
FrameRateManager* frm = visualEngine->getFrameRateManager();
|
|
|
|
return RBX::format("up %d down %d backoff %d backoff avg %d", frm->GetQualityDelayUp(), frm->GetQualityDelayDown(), frm->GetBackoffCounter(), (int)frm->GetBackoffAverage());
|
|
}
|
|
|
|
if (name == "RenderStatsFRMTargetTime")
|
|
{
|
|
FrameRateManager* frm = visualEngine->getFrameRateManager();
|
|
|
|
if (frm->GetQualityLevel() > 0 && frm->GetQualityLevel() < CRenderSettings::QualityLevelMax-1)
|
|
return RBX::format("frame %.1f render %1.f throttle %u", frm->GetTargetFrameTimeForNextLevel(), frm->GetTargetRenderTimeForNextLevel(), frm->getPhysicsThrottling());
|
|
else
|
|
return RBX::format("frame n/a render n/a throttle %u", frm->getPhysicsThrottling());
|
|
}
|
|
|
|
if (name == "RenderStatsTC")
|
|
{
|
|
TextureCompositor* tc = visualEngine->getTextureCompositor();
|
|
|
|
const TextureCompositorConfiguration& config = tc->getConfiguration();
|
|
const TextureCompositorStats& stats = tc->getStatistics();
|
|
|
|
return RBX::format("%dM hq %d (%dM) lq %d (%dM) cache %d (%dM)",
|
|
config.budget / 1048576,
|
|
stats.liveHQCount, stats.liveHQSize / 1048576,
|
|
stats.liveLQCount, stats.liveLQSize / 1048576,
|
|
stats.orphanedCount, stats.orphanedSize / 1048576);
|
|
}
|
|
|
|
if (name == "RenderStatsTM")
|
|
{
|
|
const TextureManagerStats& stats = visualEngine->getTextureManager()->getStatistics();
|
|
|
|
return RBX::format("queued %d live %d (%dM) cache %d (%dM)",
|
|
stats.queuedCount,
|
|
stats.liveCount, stats.liveSize / 1048576,
|
|
stats.orphanedCount, stats.orphanedSize / 1048576);
|
|
}
|
|
|
|
if (name == "RenderStatsLightGrid")
|
|
{
|
|
SceneUpdater* su = visualEngine->getSceneUpdater();
|
|
const RBX::WindowAverage<double,double>::Stats& stats = su->getLightingTimeStats();
|
|
|
|
if (su->isLightingActive())
|
|
return RBX::format("%d (occupancy %d, oldest: %d), %.1f ms (std: %.1f)", su->getLastLightingUpdates(), su->getLastOccupancyUpdates(), su->getLightOldestAge(), stats.average, sqrt(stats.variance));
|
|
else
|
|
return "inactive";
|
|
}
|
|
|
|
if (name == "RenderStatsGPU")
|
|
{
|
|
return RBX::format("%.1f ms (present: %.1f ms) %s", gpuAverage.getStats().average, presentAverage.getStats().average, visualEngine->getDevice()->getAPIName().c_str());
|
|
}
|
|
|
|
return "";
|
|
}
|
|
|
|
void RenderView::captureMetrics(RBX::RenderMetrics& metrics)
|
|
{
|
|
FrameRateManager* frm = visualEngine->getFrameRateManager();
|
|
|
|
metrics.presentTime = presentAverage.getStats().average;
|
|
metrics.GPUDelay = gpuAverage.getStats().average;
|
|
metrics.deltaAve = frm->GetFrameTimeAverage();
|
|
|
|
WindowAverage<double, double>::Stats renderStats = frm->GetRenderTimeStats().getSanitizedStats();
|
|
|
|
metrics.renderAve = renderStats.average;
|
|
GetConfidenceInterval(renderStats.average, renderStats.variance, C90, &metrics.renderConfidenceMin, &metrics.renderConfidenceMax);
|
|
metrics.renderStd = sqrt(renderStats.variance);
|
|
}
|
|
|
|
void RenderView::printScene()
|
|
{
|
|
}
|
|
|
|
void RenderView::reloadShaders()
|
|
{
|
|
visualEngine->reloadShaders();
|
|
}
|
|
|
|
struct ProxyMetric: IMetric
|
|
{
|
|
RenderView* view;
|
|
IMetric* parent;
|
|
|
|
ProxyMetric(RenderView* view, IMetric* parent)
|
|
: view(view), parent(parent)
|
|
{
|
|
}
|
|
|
|
virtual std::string getMetric(const std::string& metric) const
|
|
{
|
|
if (metric.compare(0, 11, "RenderStats") == 0)
|
|
return view->getRenderStatsMetric(metric);
|
|
else
|
|
return parent->getMetric(metric);
|
|
}
|
|
|
|
virtual double getMetricValue(const std::string& metric) const
|
|
{
|
|
if(metric.compare(0, 3, "FRM") == 0)
|
|
return view->getFrameRateManager()->getMetricValue(metric);
|
|
else
|
|
return parent->getMetricValue(metric);
|
|
}
|
|
};
|
|
|
|
void RenderView::renderPrepare(IMetric* metric)
|
|
{
|
|
renderPrepareImpl(metric, /* updateViewport= */ true);
|
|
}
|
|
|
|
void RenderView::renderPrepareImpl(IMetric* metric, bool updateViewport)
|
|
{
|
|
RBXPROFILER_SCOPE("Render", "Prepare");
|
|
|
|
Timer<Time::Precise> timer;
|
|
|
|
FrameRateManager* frm = visualEngine->getFrameRateManager();
|
|
|
|
fogEndTargetFRM = sqrt(frm->GetRenderCullSqDistance()); // filters out a glitch when manually switching FRM level
|
|
|
|
frm->SubmitCurrentFrame( deltaMs, totalRenderTime, prepareTime, artificialDelay);
|
|
|
|
if (dataModel->getWorkspace())
|
|
dataModel->getWorkspace()->renderingDistance = fogEndTargetFRM;
|
|
|
|
if (frameRateManagerStatsItem)
|
|
frameRateManagerStatsItem->updateData(frm);
|
|
|
|
visualEngine->reloadQueuedAssets();
|
|
|
|
double newtimeMs = Time::now(Time::Precise).timestampSeconds() * 1000;
|
|
if(timestampMs != 0)
|
|
{
|
|
deltaMs = (newtimeMs - timestampMs);
|
|
}
|
|
timestampMs = newtimeMs;
|
|
|
|
if (!visualEngine->getDevice()->validate())
|
|
return;
|
|
|
|
if (updateViewport)
|
|
{
|
|
Vector2 newViewport = computeCanvasSize(visualEngine->getDevice());
|
|
|
|
visualEngine->setViewport(newViewport.x, newViewport.y);
|
|
|
|
if (Camera* camera = dataModel->getWorkspace()->getCamera())
|
|
{
|
|
// HACK: Ideally we'd update Camera viewport in renderStep since this way renderStep has data that's one frame behind
|
|
DataModel::scoped_write_transfer request(dataModel.get());
|
|
|
|
camera->setViewport(newViewport);
|
|
}
|
|
}
|
|
|
|
dataModel->getWorkspace()->getWorld()->setFRMThrottle(frm->getPhysicsThrottling());
|
|
|
|
Lighting* lighting = ServiceProvider::find<Lighting>(dataModel.get());
|
|
RBXASSERT(lighting);
|
|
|
|
updateFog();
|
|
visualEngine->getSceneManager()->trackLightingTimeOfDay( lighting->getGameTime() );
|
|
|
|
Workspace* workspace = dataModel->getWorkspace();
|
|
RBX::Camera* cameraobj = workspace->getCamera();
|
|
|
|
Vector3 poi;
|
|
|
|
if (cameraobj->getCameraSubject())
|
|
{
|
|
poi = cameraobj->getCameraFocus().translation;
|
|
}
|
|
else
|
|
{
|
|
poi = cameraobj->getCameraCoordinateFrame().translation;
|
|
}
|
|
|
|
visualEngine->setCamera(*cameraobj, poi);
|
|
|
|
Adorn* adorn = visualEngine->getAdorn();
|
|
|
|
adorn->preSubmitPass();
|
|
|
|
if (adornsEnabled && !FFlag::DebugAdornsDisabled)
|
|
{
|
|
// HACK: Rendering code should NOT invoke Lua code or change DM in any way. But it does (in ScreenGui & Frame objects inside SurfaceGui).
|
|
DataModel::scoped_write_transfer request(dataModel.get());
|
|
|
|
// do 3d adorns first (they will actually be drawn after normal 3D pass)
|
|
dataModel->renderPass3dAdorn(adorn);
|
|
|
|
// then do 2d adorns, after 3d adorns (so that UI text shows on top)
|
|
ProxyMetric proxyMetric(this, metric);
|
|
|
|
if (FFlag::RenderUIAs3DInVR && adorn->isVR())
|
|
{
|
|
Rect2D viewport = adorn->getViewport();
|
|
|
|
CoordinateFrame cframe = cameraobj->getRenderingCoordinateFrame();
|
|
|
|
float uiFovTan = tanf(G3D::toRadians(kVRUIFOV / 2));
|
|
float uiDepth = kVRUIDepth;
|
|
float uiSize = uiDepth * uiFovTan * 2;
|
|
|
|
cframe.translation += cframe.lookVector() * uiDepth;
|
|
cframe.translation -= cframe.rightVector() * uiSize * 0.5f;
|
|
cframe.translation += cframe.upVector() * uiSize * 0.5f;
|
|
cframe.rotation *= Matrix3::fromDiagonal(Vector3(uiSize / viewport.width(), uiSize / viewport.height(), 1));
|
|
|
|
AdornBillboarder adornView(adorn, viewport, cframe, /* alwaysOnTop= */ true);
|
|
|
|
dataModel->renderPass2d(&adornView, &proxyMetric);
|
|
}
|
|
else
|
|
{
|
|
dataModel->renderPass2d(adorn, &proxyMetric);
|
|
}
|
|
}
|
|
|
|
adorn->postSubmitPass();
|
|
|
|
updateLighting(lighting);
|
|
|
|
float dt = frm->GetFrameTimeStats().getLatest() / 1000.f;
|
|
|
|
visualEngine->getWater()->update(dataModel.get(), dt);
|
|
|
|
visualEngine->getSceneUpdater()->updatePrepare(0, *visualEngine->getUpdateFrustum());
|
|
|
|
visualEngine->getTextureCompositor()->update(poi);
|
|
|
|
#if defined(RBX_PLATFORM_DURANGO)
|
|
if(RBX::PlatformService* platformService = ServiceProvider::find<PlatformService>(dataModel.get()))
|
|
{
|
|
presetPostProcess(platformService);
|
|
}
|
|
#endif
|
|
|
|
// Pass FRM configuration to shaders
|
|
GlobalShaderData& globalShaderData = visualEngine->getSceneManager()->writeGlobalShaderData();
|
|
|
|
float shadingDistance = std::max(frm->getShadingDistance(), 0.1f);
|
|
float fovCoefficient = visualEngine->getCamera().getProjectionMatrix()[1][1];
|
|
float outlineScaling = fovCoefficient * visualEngine->getViewHeight() * (10.0f / FInt::OutlineThickness) * (1/2.0f) / shadingDistance;
|
|
|
|
float brightnessMulFactor = outlinesEnabled ? (FInt::OutlineBrightnessMax - FInt::OutlineBrightnessMin) / 255.0f : 0.0f;
|
|
float brightnessAddFactor = outlinesEnabled ? FInt::OutlineBrightnessMin / 255.0f : 1.0f;
|
|
|
|
float shadowFade = powf(G3D::clamp(lighting->getSkyParameters().lightDirection.unit().y, 0.f, 1.f), 1.f / 4);
|
|
float shadowIntensity = shadowFade * (FInt::RenderShadowIntensity / 100.f);
|
|
|
|
globalShaderData.FadeDistance_GlowFactor = Vector4(shadingDistance, 1.f / shadingDistance, outlineScaling, globalShaderData.FadeDistance_GlowFactor.w);
|
|
globalShaderData.OutlineBrightness_ShadowInfo = Vector4(brightnessMulFactor, brightnessAddFactor, 0, shadowIntensity);
|
|
|
|
FASTLOG(FLog::ViewRbxBase, "Render prepare end");
|
|
prepareTime = timer.delta().msec();
|
|
prepareAverage.sample(prepareTime);
|
|
}
|
|
|
|
void RenderView::drawRecordingFrame(DeviceContext* context)
|
|
{
|
|
RBXASSERT(videoFrameVertexStreamer);
|
|
|
|
Framebuffer* fb = visualEngine->getDevice()->getMainFramebuffer();
|
|
|
|
static const unsigned lineWidth = 2;
|
|
unsigned w = fb->getWidth();
|
|
unsigned h = fb->getHeight();
|
|
|
|
Rect2D screen = Rect2D::xywh(0, 0, w, h);
|
|
Rect2D paddedScreen = Rect2D::xywh(lineWidth, lineWidth, w - lineWidth * 2, h - lineWidth * 2);
|
|
|
|
videoFrameVertexStreamer->rectBlt(shared_ptr<Texture>(), Color4(1,0,0), screen.corner(3), paddedScreen.corner(3), screen.corner(0), paddedScreen.corner(0), Vector2::zero(), Vector2::zero(), BatchTextureType_Color, true);
|
|
videoFrameVertexStreamer->rectBlt(shared_ptr<Texture>(), Color4(1,0,0), paddedScreen.corner(2), screen.corner(2), paddedScreen.corner(1), screen.corner(1), Vector2::zero(), Vector2::zero(), BatchTextureType_Color, true);
|
|
videoFrameVertexStreamer->rectBlt(shared_ptr<Texture>(), Color4(1,0,0), paddedScreen.corner(0), paddedScreen.corner(1), screen.corner(0), screen.corner(1), Vector2::zero(), Vector2::zero(), BatchTextureType_Color, true);
|
|
videoFrameVertexStreamer->rectBlt(shared_ptr<Texture>(), Color4(1,0,0), screen.corner(3), screen.corner(2), paddedScreen.corner(3), paddedScreen.corner(2), Vector2::zero(), Vector2::zero(), BatchTextureType_Color, true);
|
|
|
|
videoFrameVertexStreamer->renderPrepare();
|
|
videoFrameVertexStreamer->render2D(context, w, h, visualEngine->getRenderStats()->passUI);
|
|
videoFrameVertexStreamer->renderFinish();
|
|
}
|
|
|
|
void RenderView::drawVRWindow(DeviceContext* context)
|
|
{
|
|
RBXPROFILER_SCOPE("GPU", "Window");
|
|
|
|
DeviceVR* vr = visualEngine->getDevice()->getVR();
|
|
Framebuffer* mainFramebuffer = visualEngine->getDevice()->getMainFramebuffer();
|
|
Framebuffer* eyeFramebuffer = vr->getEyeFramebuffer(0);
|
|
|
|
if (!vrVertexStreamer)
|
|
vrVertexStreamer.reset(new VertexStreamer(visualEngine.get()));
|
|
|
|
if (!vrDebugTexture || vrDebugTexture->getWidth() != eyeFramebuffer->getWidth() || vrDebugTexture->getHeight() != eyeFramebuffer->getHeight())
|
|
vrDebugTexture = visualEngine->getDevice()->createTexture(Texture::Type_2D, Texture::Format_RGBA8, eyeFramebuffer->getWidth(), eyeFramebuffer->getHeight(), 1, 1, Texture::Usage_Renderbuffer);
|
|
|
|
int w = mainFramebuffer->getWidth();
|
|
int h = mainFramebuffer->getHeight();
|
|
|
|
int x = (w - h) / 2;
|
|
|
|
const float clearColor[4] = {};
|
|
|
|
context->copyFramebuffer(eyeFramebuffer, vrDebugTexture.get());
|
|
|
|
context->bindFramebuffer(mainFramebuffer);
|
|
context->clearFramebuffer(DeviceContext::Buffer_Color, clearColor, 1.f, 0);
|
|
|
|
vrVertexStreamer->rectBlt(vrDebugTexture, Color4(1,1,1),
|
|
Vector2(x,h), Vector2(w - x,h),
|
|
Vector2(x,0), Vector2(w - x,0),
|
|
Vector2(0,0), Vector2(1,1), BatchTextureType_Color, false);
|
|
|
|
vrVertexStreamer->renderPrepare();
|
|
vrVertexStreamer->render2D(context, w, h, visualEngine->getRenderStats()->passUI);
|
|
vrVertexStreamer->renderFinish();
|
|
}
|
|
|
|
namespace
|
|
{
|
|
const int g_MicroProfileFontTextureX = 1024;
|
|
const int g_MicroProfileFontTextureY = 9;
|
|
|
|
const uint16_t g_MicroProfileFontDescription[] =
|
|
{
|
|
0x0ce,0x0ce,0x0ce,0x0ce,0x0ce,0x0ce,0x0ce,0x0ce,0x0ce,0x0ce,0x0ce,0x0ce,0x0ce,0x0ce,0x0ce,0x0ce,
|
|
0x0ce,0x0ce,0x0ce,0x0ce,0x0ce,0x0ce,0x0ce,0x0ce,0x0ce,0x0ce,0x0ce,0x0ce,0x0ce,0x0ce,0x0ce,0x0ce,
|
|
0x0ce,0x201,0x209,0x211,0x219,0x221,0x229,0x231,0x239,0x241,0x249,0x251,0x259,0x261,0x269,0x271,
|
|
0x1b1,0x1b9,0x1c1,0x1c9,0x1d1,0x1d9,0x1e1,0x1e9,0x1f1,0x1f9,0x279,0x281,0x289,0x291,0x299,0x2a1,
|
|
0x2a9,0x001,0x009,0x011,0x019,0x021,0x029,0x031,0x039,0x041,0x049,0x051,0x059,0x061,0x069,0x071,
|
|
0x079,0x081,0x089,0x091,0x099,0x0a1,0x0a9,0x0b1,0x0b9,0x0c1,0x0c9,0x2b1,0x2b9,0x2c1,0x2c9,0x2d1,
|
|
0x0ce,0x0d9,0x0e1,0x0e9,0x0f1,0x0f9,0x101,0x109,0x111,0x119,0x121,0x129,0x131,0x139,0x141,0x149,
|
|
0x151,0x159,0x161,0x169,0x171,0x179,0x181,0x189,0x191,0x199,0x1a1,0x2d9,0x2e1,0x2e9,0x2f1,0x0ce,
|
|
0x0ce,0x0ce,0x0ce,0x0ce,0x0ce,0x0ce,0x0ce,0x0ce,0x0ce,0x0ce,0x0ce,0x0ce,0x0ce,0x0ce,0x0ce,0x0ce,
|
|
0x0ce,0x0ce,0x0ce,0x0ce,0x0ce,0x0ce,0x0ce,0x0ce,0x0ce,0x0ce,0x0ce,0x0ce,0x0ce,0x0ce,0x0ce,0x0ce,
|
|
0x0ce,0x0ce,0x0ce,0x0ce,0x0ce,0x0ce,0x0ce,0x0ce,0x0ce,0x0ce,0x0ce,0x0ce,0x0ce,0x0ce,0x0ce,0x0ce,
|
|
0x0ce,0x0ce,0x0ce,0x0ce,0x0ce,0x0ce,0x0ce,0x0ce,0x0ce,0x0ce,0x0ce,0x0ce,0x0ce,0x0ce,0x0ce,0x0ce,
|
|
0x0ce,0x0ce,0x0ce,0x0ce,0x0ce,0x0ce,0x0ce,0x0ce,0x0ce,0x0ce,0x0ce,0x0ce,0x0ce,0x0ce,0x0ce,0x0ce,
|
|
0x0ce,0x0ce,0x0ce,0x0ce,0x0ce,0x0ce,0x0ce,0x0ce,0x0ce,0x0ce,0x0ce,0x0ce,0x0ce,0x0ce,0x0ce,0x0ce,
|
|
0x0ce,0x0ce,0x0ce,0x0ce,0x0ce,0x0ce,0x0ce,0x0ce,0x0ce,0x0ce,0x0ce,0x0ce,0x0ce,0x0ce,0x0ce,0x0ce,
|
|
0x0ce,0x0ce,0x0ce,0x0ce,0x0ce,0x0ce,0x0ce,0x0ce,0x0ce,0x0ce,0x0ce,0x0ce,0x0ce,0x0ce,0x0ce,0x0ce,
|
|
};
|
|
|
|
const uint8_t g_MicroProfileFont[] =
|
|
{
|
|
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
|
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
|
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
|
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
|
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
|
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
|
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
|
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
|
0x10,0x78,0x38,0x78,0x7c,0x7c,0x3c,0x44,0x38,0x04,0x44,0x40,0x44,0x44,0x38,0x78,
|
|
0x38,0x78,0x38,0x7c,0x44,0x44,0x44,0x44,0x44,0x7c,0x00,0x00,0x40,0x00,0x04,0x00,
|
|
0x18,0x00,0x40,0x10,0x08,0x40,0x30,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x10,0x00,
|
|
0x00,0x00,0x00,0x00,0x00,0x00,0x38,0x10,0x38,0x7c,0x08,0x7c,0x1c,0x7c,0x38,0x38,
|
|
0x10,0x28,0x28,0x10,0x00,0x20,0x10,0x08,0x10,0x10,0x00,0x00,0x00,0x00,0x00,0x00,
|
|
0x00,0x04,0x00,0x20,0x38,0x38,0x70,0x00,0x1c,0x10,0x00,0x1c,0x10,0x70,0x30,0x00,
|
|
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
|
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
|
0x28,0x44,0x44,0x44,0x40,0x40,0x40,0x44,0x10,0x04,0x48,0x40,0x6c,0x44,0x44,0x44,
|
|
0x44,0x44,0x44,0x10,0x44,0x44,0x44,0x44,0x44,0x04,0x00,0x00,0x40,0x00,0x04,0x00,
|
|
0x24,0x00,0x40,0x00,0x00,0x40,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x10,0x00,
|
|
0x00,0x00,0x00,0x00,0x00,0x00,0x44,0x30,0x44,0x04,0x18,0x40,0x20,0x04,0x44,0x44,
|
|
0x10,0x28,0x28,0x3c,0x44,0x50,0x10,0x10,0x08,0x54,0x10,0x00,0x00,0x00,0x04,0x00,
|
|
0x00,0x08,0x00,0x10,0x44,0x44,0x40,0x40,0x04,0x28,0x00,0x30,0x10,0x18,0x58,0x00,
|
|
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
|
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
|
0x44,0x44,0x40,0x44,0x40,0x40,0x40,0x44,0x10,0x04,0x50,0x40,0x54,0x64,0x44,0x44,
|
|
0x44,0x44,0x40,0x10,0x44,0x44,0x44,0x28,0x28,0x08,0x00,0x38,0x78,0x3c,0x3c,0x38,
|
|
0x20,0x38,0x78,0x30,0x18,0x44,0x10,0x6c,0x78,0x38,0x78,0x3c,0x5c,0x3c,0x3c,0x44,
|
|
0x44,0x44,0x44,0x44,0x7c,0x00,0x4c,0x10,0x04,0x08,0x28,0x78,0x40,0x08,0x44,0x44,
|
|
0x10,0x00,0x7c,0x50,0x08,0x50,0x00,0x20,0x04,0x38,0x10,0x00,0x00,0x00,0x08,0x10,
|
|
0x10,0x10,0x7c,0x08,0x08,0x54,0x40,0x20,0x04,0x44,0x00,0x30,0x10,0x18,0x00,0x00,
|
|
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
|
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
|
0x44,0x78,0x40,0x44,0x78,0x78,0x40,0x7c,0x10,0x04,0x60,0x40,0x54,0x54,0x44,0x78,
|
|
0x44,0x78,0x38,0x10,0x44,0x44,0x54,0x10,0x10,0x10,0x00,0x04,0x44,0x40,0x44,0x44,
|
|
0x78,0x44,0x44,0x10,0x08,0x48,0x10,0x54,0x44,0x44,0x44,0x44,0x60,0x40,0x10,0x44,
|
|
0x44,0x44,0x28,0x44,0x08,0x00,0x54,0x10,0x18,0x18,0x48,0x04,0x78,0x10,0x38,0x3c,
|
|
0x10,0x00,0x28,0x38,0x10,0x20,0x00,0x20,0x04,0x10,0x7c,0x00,0x7c,0x00,0x10,0x00,
|
|
0x00,0x20,0x00,0x04,0x10,0x5c,0x40,0x10,0x04,0x00,0x00,0x60,0x10,0x0c,0x00,0x00,
|
|
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
|
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
|
0x7c,0x44,0x40,0x44,0x40,0x40,0x4c,0x44,0x10,0x04,0x50,0x40,0x44,0x4c,0x44,0x40,
|
|
0x54,0x50,0x04,0x10,0x44,0x44,0x54,0x28,0x10,0x20,0x00,0x3c,0x44,0x40,0x44,0x7c,
|
|
0x20,0x44,0x44,0x10,0x08,0x70,0x10,0x54,0x44,0x44,0x44,0x44,0x40,0x38,0x10,0x44,
|
|
0x44,0x54,0x10,0x44,0x10,0x00,0x64,0x10,0x20,0x04,0x7c,0x04,0x44,0x20,0x44,0x04,
|
|
0x10,0x00,0x7c,0x14,0x20,0x54,0x00,0x20,0x04,0x38,0x10,0x10,0x00,0x00,0x20,0x10,
|
|
0x10,0x10,0x7c,0x08,0x10,0x58,0x40,0x08,0x04,0x00,0x00,0x30,0x10,0x18,0x00,0x00,
|
|
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
|
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
|
0x44,0x44,0x44,0x44,0x40,0x40,0x44,0x44,0x10,0x44,0x48,0x40,0x44,0x44,0x44,0x40,
|
|
0x48,0x48,0x44,0x10,0x44,0x28,0x6c,0x44,0x10,0x40,0x00,0x44,0x44,0x40,0x44,0x40,
|
|
0x20,0x3c,0x44,0x10,0x08,0x48,0x10,0x54,0x44,0x44,0x44,0x44,0x40,0x04,0x12,0x4c,
|
|
0x28,0x54,0x28,0x3c,0x20,0x00,0x44,0x10,0x40,0x44,0x08,0x44,0x44,0x20,0x44,0x08,
|
|
0x00,0x00,0x28,0x78,0x44,0x48,0x00,0x10,0x08,0x54,0x10,0x10,0x00,0x00,0x40,0x00,
|
|
0x10,0x08,0x00,0x10,0x00,0x40,0x40,0x04,0x04,0x00,0x00,0x30,0x10,0x18,0x00,0x00,
|
|
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
|
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
|
0x44,0x78,0x38,0x78,0x7c,0x40,0x3c,0x44,0x38,0x38,0x44,0x7c,0x44,0x44,0x38,0x40,
|
|
0x34,0x44,0x38,0x10,0x38,0x10,0x44,0x44,0x10,0x7c,0x00,0x3c,0x78,0x3c,0x3c,0x3c,
|
|
0x20,0x04,0x44,0x38,0x48,0x44,0x38,0x44,0x44,0x38,0x78,0x3c,0x40,0x78,0x0c,0x34,
|
|
0x10,0x6c,0x44,0x04,0x7c,0x00,0x38,0x38,0x7c,0x38,0x08,0x38,0x38,0x20,0x38,0x70,
|
|
0x10,0x00,0x28,0x10,0x00,0x34,0x00,0x08,0x10,0x10,0x00,0x20,0x00,0x10,0x00,0x00,
|
|
0x20,0x04,0x00,0x20,0x10,0x3c,0x70,0x00,0x1c,0x00,0x7c,0x1c,0x10,0x70,0x00,0x00,
|
|
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
|
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
|
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
|
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
|
0x00,0x38,0x00,0x00,0x30,0x00,0x00,0x00,0x00,0x00,0x40,0x04,0x00,0x00,0x00,0x00,
|
|
0x00,0x00,0x00,0x38,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
|
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
|
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
|
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
|
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
|
};
|
|
}
|
|
|
|
class ProfilerRenderer: public Profiler::Renderer
|
|
{
|
|
public:
|
|
ProfilerRenderer(VisualEngine* visualEngine)
|
|
: visualEngine(visualEngine)
|
|
, colorOrderBGR(false)
|
|
{
|
|
Device* device = visualEngine->getDevice();
|
|
|
|
colorOrderBGR = device->getCaps().colorOrderBGR;
|
|
|
|
std::vector<VertexLayout::Element> elements;
|
|
elements.push_back(VertexLayout::Element(0, offsetof(Vertex, x), VertexLayout::Format_Float2, VertexLayout::Semantic_Position));
|
|
elements.push_back(VertexLayout::Element(0, offsetof(Vertex, u), VertexLayout::Format_Float2, VertexLayout::Semantic_Texture));
|
|
elements.push_back(VertexLayout::Element(0, offsetof(Vertex, color), VertexLayout::Format_Color, VertexLayout::Semantic_Color));
|
|
|
|
layout = device->createVertexLayout(elements);
|
|
|
|
const size_t fontSize = g_MicroProfileFontTextureX * g_MicroProfileFontTextureY * 4;
|
|
|
|
uint32_t* pUnpacked = (uint32_t*)alloca(fontSize);
|
|
|
|
int idx = 0;
|
|
int end = g_MicroProfileFontTextureX * g_MicroProfileFontTextureY / 8;
|
|
for (int i = 0; i < end; i++)
|
|
{
|
|
unsigned char pValue = g_MicroProfileFont[i];
|
|
for (int j = 0; j < 8; ++j)
|
|
{
|
|
pUnpacked[idx++] = (pValue & 0x80) ? 0xffffffff : 0;
|
|
pValue <<= 1;
|
|
}
|
|
}
|
|
|
|
pUnpacked[idx-1] = 0xffffffff;
|
|
|
|
fontTexture = device->createTexture(Texture::Type_2D, Texture::Format_RGBA8, g_MicroProfileFontTextureX, g_MicroProfileFontTextureY, 1, 1, Texture::Usage_Static);
|
|
|
|
fontTexture->upload(0, 0, TextureRegion(0, 0, g_MicroProfileFontTextureX, g_MicroProfileFontTextureY), pUnpacked, fontSize);
|
|
}
|
|
|
|
void drawText(int x, int y, unsigned int color, const char* text, unsigned int length, unsigned int textWidth, unsigned int textHeight) override
|
|
{
|
|
const float fOffsetU = float(textWidth) / float(g_MicroProfileFontTextureX);
|
|
|
|
float fX = (float)x;
|
|
float fY = (float)y;
|
|
float fY2 = fY + (textHeight+1);
|
|
|
|
const char* pStr = text;
|
|
|
|
if (!colorOrderBGR)
|
|
color = 0xff000000|((color&0xff)<<16)|(color&0xff00)|((color>>16)&0xff);
|
|
|
|
for (uint32_t j = 0; j < length; ++j)
|
|
{
|
|
int16_t nOffset = g_MicroProfileFontDescription[uint8_t(*pStr++)];
|
|
float fOffset = float(nOffset) / float(g_MicroProfileFontTextureX);
|
|
|
|
Vertex v0 = { fX, fY, fOffset, 0.f, color };
|
|
Vertex v1 = { fX + textWidth, fY, fOffset + fOffsetU, 0.f, color };
|
|
Vertex v2 = { fX + textWidth, fY2, fOffset + fOffsetU, 1.f, color };
|
|
Vertex v3 = { fX, fY2, fOffset, 1.f, color };
|
|
|
|
quads.push_back(v0);
|
|
quads.push_back(v1);
|
|
quads.push_back(v3);
|
|
quads.push_back(v1);
|
|
quads.push_back(v2);
|
|
quads.push_back(v3);
|
|
|
|
fX += textWidth+1;
|
|
}
|
|
}
|
|
|
|
void drawBox(int x0, int y0, int x1, int y1, unsigned int color0, unsigned int color1) override
|
|
{
|
|
if (!colorOrderBGR)
|
|
{
|
|
color0 = ((color0&0xff)<<16)|((color0>>16)&0xff)|(0xff00ff00&color0);
|
|
color1 = ((color1&0xff)<<16)|((color1>>16)&0xff)|(0xff00ff00&color1);
|
|
}
|
|
|
|
Vertex v0 = { (float)x0, (float)y0, 2.f, 2.f, color0 };
|
|
Vertex v1 = { (float)x1, (float)y0, 2.f, 2.f, color0 };
|
|
Vertex v2 = { (float)x1, (float)y1, 2.f, 2.f, color1 };
|
|
Vertex v3 = { (float)x0, (float)y1, 2.f, 2.f, color1 };
|
|
|
|
quads.push_back(v0);
|
|
quads.push_back(v1);
|
|
quads.push_back(v3);
|
|
quads.push_back(v1);
|
|
quads.push_back(v2);
|
|
quads.push_back(v3);
|
|
}
|
|
|
|
void drawLine(unsigned int vertexCount, const float* vertexData, unsigned int color) override
|
|
{
|
|
if (!colorOrderBGR)
|
|
color = ((color&0xff)<<16)|((color>>16)&0xff)|(0xff00ff00&color);
|
|
|
|
for (unsigned int i = 0; i + 1 < vertexCount; ++i)
|
|
{
|
|
Vertex v0 = { vertexData[2 * i + 0], vertexData[2 * i + 1], 2.f, 2.f, color };
|
|
Vertex v1 = { vertexData[2 * i + 2], vertexData[2 * i + 3], 2.f, 2.f, color };
|
|
|
|
lines.push_back(v0);
|
|
lines.push_back(v1);
|
|
}
|
|
}
|
|
|
|
void flush(DeviceContext* context, const RenderCamera& camera)
|
|
{
|
|
if (quads.empty() && lines.empty())
|
|
return;
|
|
|
|
updateBuffer(quadsVB, quadsGeometry, quads);
|
|
updateBuffer(linesVB, linesGeometry, lines);
|
|
|
|
if (shared_ptr<ShaderProgram> program = visualEngine->getShaderManager()->getProgram("ProfilerVS", "ProfilerFS"))
|
|
{
|
|
GlobalShaderData shaderData = visualEngine->getSceneManager()->readGlobalShaderData();
|
|
|
|
shaderData.setCamera(camera);
|
|
|
|
context->updateGlobalConstants(&shaderData, sizeof(GlobalShaderData));
|
|
|
|
context->bindProgram(program.get());
|
|
|
|
context->setRasterizerState(RasterizerState::Cull_None);
|
|
context->setBlendState(BlendState::Mode_AlphaBlend);
|
|
context->setDepthState(DepthState(DepthState::Function_Always, false));
|
|
|
|
context->bindTexture(0, fontTexture.get(), SamplerState(SamplerState::Filter_Linear, SamplerState::Address_Clamp));
|
|
|
|
if (!quads.empty())
|
|
context->draw(quadsGeometry.get(), Geometry::Primitive_Triangles, 0, quads.size(), 0, quads.size());
|
|
|
|
if (!lines.empty())
|
|
context->draw(linesGeometry.get(), Geometry::Primitive_Lines, 0, lines.size(), 0, lines.size());
|
|
}
|
|
|
|
quads.clear();
|
|
lines.clear();
|
|
}
|
|
|
|
private:
|
|
struct Vertex
|
|
{
|
|
float x, y;
|
|
float u, v;
|
|
unsigned int color;
|
|
};
|
|
|
|
VisualEngine* visualEngine;
|
|
|
|
bool colorOrderBGR;
|
|
|
|
std::vector<Vertex> quads;
|
|
std::vector<Vertex> lines;
|
|
|
|
shared_ptr<Texture> fontTexture;
|
|
|
|
shared_ptr<VertexLayout> layout;
|
|
|
|
shared_ptr<VertexBuffer> quadsVB;
|
|
shared_ptr<Geometry> quadsGeometry;
|
|
|
|
shared_ptr<VertexBuffer> linesVB;
|
|
shared_ptr<Geometry> linesGeometry;
|
|
|
|
void updateBuffer(shared_ptr<VertexBuffer>& vb, shared_ptr<Geometry>& geometry, const std::vector<Vertex>& vertices)
|
|
{
|
|
if (vertices.empty()) return;
|
|
|
|
if (!vb || vb->getElementCount() < vertices.size())
|
|
{
|
|
size_t count = 1;
|
|
while (count < vertices.size())
|
|
count *= 2;
|
|
|
|
vb = visualEngine->getDevice()->createVertexBuffer(sizeof(Vertex), count, GeometryBuffer::Usage_Dynamic);
|
|
geometry = visualEngine->getDevice()->createGeometry(layout, vb, shared_ptr<IndexBuffer>());
|
|
}
|
|
|
|
void* locked = vb->lock(VertexBuffer::Lock_Discard);
|
|
memcpy(locked, vertices.data(), vertices.size() * sizeof(Vertex));
|
|
vb->unlock();
|
|
}
|
|
};
|
|
|
|
void RenderView::drawProfiler(DeviceContext* context)
|
|
{
|
|
if (!Profiler::isVisible())
|
|
return;
|
|
|
|
if (!profilerRenderer)
|
|
profilerRenderer.reset(new ProfilerRenderer(visualEngine.get()));
|
|
|
|
RBXPROFILER_SCOPE("GPU", "Profiler");
|
|
|
|
Framebuffer* fb = visualEngine->getDevice()->getMainFramebuffer();
|
|
|
|
Profiler::render(profilerRenderer.get(), fb->getWidth(), fb->getHeight());
|
|
|
|
RenderCamera camera;
|
|
camera.setViewMatrix(Matrix4::identity());
|
|
camera.setProjectionOrtho(fb->getWidth(), fb->getHeight(), -1, 1, visualEngine->getDevice()->getCaps().needsHalfPixelOffset);
|
|
|
|
profilerRenderer->flush(context, camera);
|
|
}
|
|
|
|
void RenderView::renderPerform(double timeJobStart)
|
|
{
|
|
renderPerformImpl(timeJobStart, visualEngine->getDevice()->getMainFramebuffer());
|
|
}
|
|
|
|
void RenderView::renderPerformImpl(double timeJobStart, Framebuffer* mainFramebuffer)
|
|
{
|
|
RBXPROFILER_SCOPE("Render", "Perform");
|
|
|
|
if( !this->dataModel )
|
|
return;
|
|
|
|
Timer<Time::Precise> timer;
|
|
|
|
FASTLOG(FLog::ViewRbxBase, "Render perform start");
|
|
|
|
double performTime = 0;
|
|
double presentTime = 0;
|
|
|
|
Adorn* adorn = visualEngine->getAdorn();
|
|
|
|
// update glyph atlas
|
|
if (FFlag::UseDynamicTypesetterUTF8)
|
|
visualEngine->getGlyphAtlas()->upload();
|
|
|
|
adorn->prepareRenderPass();
|
|
|
|
if (DeviceContext* context = visualEngine->getDevice()->beginFrame())
|
|
{
|
|
visualEngine->getSceneUpdater()->updatePerform();
|
|
|
|
visualEngine->getMaterialGenerator()->garbageCollectIncremental();
|
|
visualEngine->getTextureManager()->garbageCollectIncremental();
|
|
|
|
visualEngine->getTextureManager()->processPendingRequests();
|
|
|
|
context->setDefaultAnisotropy(std::max(1, visualEngine->getFrameRateManager()->getTextureAnisotropy()));
|
|
|
|
visualEngine->getTextureCompositor()->render(context);
|
|
|
|
if (DeviceVR* vr = visualEngine->getDevice()->getVR())
|
|
{
|
|
RBXPROFILER_SCOPE("GPU", "Scene");
|
|
|
|
DeviceVR::State vrState = vr->getState();
|
|
|
|
const RenderCamera& cullCamera = visualEngine->getCameraCull();
|
|
|
|
float zNear = 0.5f;
|
|
float zFar = 5000.f;
|
|
|
|
visualEngine->getSceneManager()->renderBegin(context, cullCamera, vr->getEyeFramebuffer(0)->getWidth(), vr->getEyeFramebuffer(0)->getHeight());
|
|
|
|
for (int eye = 0; eye < 2; ++eye)
|
|
{
|
|
RBXPROFILER_SCOPE("GPU", "Eye");
|
|
|
|
Framebuffer* eyeFB = vr->getEyeFramebuffer(eye);
|
|
|
|
Vector3 eyeOffset = Vector3(vrState.eyeOffset[eye]) * kVRScale;
|
|
|
|
RenderCamera eyeCamera = visualEngine->getCamera();
|
|
|
|
eyeCamera.setViewMatrix(Matrix4::translation(-eyeOffset) * eyeCamera.getViewMatrix());
|
|
eyeCamera.setProjectionPerspective(vrState.eyeFov[eye][0], vrState.eyeFov[eye][1], vrState.eyeFov[eye][2], vrState.eyeFov[eye][3], zNear, zFar);
|
|
|
|
visualEngine->getSceneManager()->renderView(context, eyeFB, eyeCamera, eyeFB->getWidth(), eyeFB->getHeight());
|
|
}
|
|
|
|
visualEngine->getSceneManager()->renderEnd(context);
|
|
|
|
if (vrState.needsMirror)
|
|
drawVRWindow(context);
|
|
}
|
|
else
|
|
{
|
|
visualEngine->getSceneManager()->renderScene(context, mainFramebuffer, visualEngine->getCamera(), visualEngine->getViewWidth(), visualEngine->getViewHeight());
|
|
}
|
|
|
|
if ((!DFFlag::ScreenShotDuplicationFix && doScreenshot) || (DFFlag::ScreenShotDuplicationFix && getAndClearDoScreenshot()))
|
|
{
|
|
std::string screenshotFilename;
|
|
if (saveScreenshotToFile(screenshotFilename))
|
|
{
|
|
if (dataModel)
|
|
{
|
|
dataModel->submitTask(boost::bind(&RBX::DataModel::ScreenshotReadyTask, weak_ptr<RBX::DataModel>(dataModel), screenshotFilename), RBX::DataModelJob::Write);
|
|
}
|
|
}
|
|
}
|
|
|
|
performTime = timer.reset().msec();
|
|
performAverage.sample(performTime);
|
|
|
|
if (videoFrameVertexStreamer && frameDataCallback)
|
|
{
|
|
frameDataCallback(visualEngine->getDevice());
|
|
// this has to be checked again, as frameDataCallback can stop video recording and NULL it
|
|
if (videoFrameVertexStreamer)
|
|
drawRecordingFrame(context);
|
|
}
|
|
|
|
drawProfiler(context);
|
|
|
|
RBXPROFILER_SCOPE("Render", "Present");
|
|
|
|
visualEngine->getDevice()->endFrame();
|
|
|
|
presentTime = timer.delta().msec();
|
|
presentAverage.sample(presentTime);
|
|
|
|
gpuAverage.sample(visualEngine->getDevice()->getStatistics().gpuFrameTime);
|
|
}
|
|
|
|
adorn->finishRenderPass();
|
|
|
|
totalRenderTime = (Time::nowFastSec() - timeJobStart)*1000.0;
|
|
|
|
FASTLOG4F(FLog::ViewRbxBase, "Render perform end. Delta: %f, Total %f, Present: %f, Prepare: %f", deltaMs, totalRenderTime, presentTime, prepareTime);
|
|
}
|
|
|
|
void RenderView::buildGui(bool buildInGameGui)
|
|
{
|
|
dataModel->startCoreScripts(buildInGameGui);
|
|
}
|
|
|
|
RenderStats & RenderView::getRenderStats() { return *visualEngine->getRenderStats(); }
|
|
|
|
void RenderView::presetLighting(RBX::Lighting* l, const RBX::Color3& extraAmbient, float skylightFactor)
|
|
{
|
|
outlinesEnabled = l->getOutlines();
|
|
|
|
const G3D::LightingParameters& skyParameters = l->getSkyParameters();
|
|
SceneManager *smgr = visualEngine->getSceneManager();
|
|
|
|
Color3 bottomColorShift = l->getBottomColorShift();
|
|
Color3 topColorShift = l->getTopColorShift();
|
|
|
|
float bottomShiftBase = (bottomColorShift.r+bottomColorShift.g+bottomColorShift.b)*0.3333f;
|
|
Color3 bottomAdjustment = bottomColorShift - Color3(bottomShiftBase,bottomShiftBase, bottomShiftBase);
|
|
|
|
float topShiftBase = (topColorShift.r+topColorShift.g+topColorShift.b)*0.3333f;
|
|
Color3 topAdjustment = topColorShift - Color3(topShiftBase,topShiftBase, topShiftBase);
|
|
|
|
float globalBrightness = G3D::clamp(l->getGlobalBrightness(), 0.05f, 5.0f);
|
|
|
|
Vector3 sunDirection = -skyParameters.lightDirection.unit();
|
|
Color3 sunColor = skyParameters.lightColor * 0.9f;
|
|
|
|
Color3 ambientColor = l->getGlobalAmbient() + extraAmbient;
|
|
|
|
Color3 keyLightColor = sunColor * globalBrightness;
|
|
keyLightColor += topAdjustment * keyLightColor.length();
|
|
keyLightColor *= skylightFactor;
|
|
|
|
Color3 fillLightColor = sunColor * globalBrightness * 0.4f;
|
|
fillLightColor += bottomAdjustment * fillLightColor.length();
|
|
fillLightColor *= skylightFactor;
|
|
|
|
smgr->setLighting(ambientColor, sunDirection, keyLightColor.min(Color3::white()), fillLightColor.min(Color3::white()));
|
|
}
|
|
|
|
void RenderView::presetPostProcess(RBX::PlatformService* platformService)
|
|
{
|
|
#if defined(RBX_PLATFORM_DURANGO)
|
|
SceneManager *smgr = visualEngine->getSceneManager();
|
|
|
|
smgr->setPostProcess(platformService->brightness, platformService->contrast, platformService->grayscaleLevel, platformService->blurIntensity, platformService->tintColor);
|
|
#endif
|
|
}
|
|
|
|
static void waitForContent(RBX::ContentProvider* contentProvider)
|
|
{
|
|
boost::xtime expirationTime;
|
|
boost::xtime_get(&expirationTime, boost::TIME_UTC_);
|
|
expirationTime.sec += static_cast<boost::xtime::xtime_sec_t>(120);
|
|
|
|
// TODO: Bail out after a certain number of seconds...
|
|
while (!contentProvider->isRequestQueueEmpty())
|
|
{
|
|
boost::xtime xt;
|
|
boost::xtime_get(&xt, boost::TIME_UTC_);
|
|
|
|
if(xtime_cmp(xt, expirationTime) == 1)
|
|
throw RBX::runtime_error("Timeout while waiting for content - 120 seconds");
|
|
|
|
boost::this_thread::sleep(boost::posix_time::milliseconds(20));
|
|
}
|
|
}
|
|
|
|
static void modifyThumbnailCamera(VisualEngine* visualEngine, bool allowDolly, int recdepth = 1)
|
|
{
|
|
if (recdepth > 10)
|
|
return;
|
|
|
|
std::vector<CullableSceneNode*> nodes = visualEngine->getSceneManager()->getSpatialHashedScene()->getAllNodes();
|
|
|
|
Extents extents;
|
|
|
|
for (size_t i = 0; i < nodes.size(); ++i)
|
|
if (!dynamic_cast<LightObject*>(nodes[i]))
|
|
extents.expandToContain(nodes[i]->getWorldBounds());
|
|
|
|
Matrix4 viewProj = visualEngine->getCamera().getViewProjectionMatrix();
|
|
|
|
Extents screen;
|
|
|
|
for (unsigned i = 0; i < 8; ++i)
|
|
{
|
|
Vector4 p = viewProj * Vector4(extents.getCorner(i), 1);
|
|
Vector3 q = p.xyz() / p.w;
|
|
|
|
if (p.w <= 0.001f || fabsf(q.x) > 1 || fabsf(q.y) > 1 || fabsf(q.z) > 1 )
|
|
{
|
|
if( !allowDolly )
|
|
return;
|
|
|
|
Matrix4 view = visualEngine->getCameraMutable().getViewMatrix();
|
|
Matrix4 dolly = Matrix4::translation(0,0,-1-recdepth*recdepth);
|
|
visualEngine->getCameraMutable().setViewMatrix( dolly * view );
|
|
return modifyThumbnailCamera(visualEngine,true,recdepth+1); // try once again
|
|
}
|
|
|
|
screen.expandToContain(q);
|
|
}
|
|
|
|
float zoomH = 2 / (screen.max().x - screen.min().x);
|
|
float zoomV = 2 / (screen.max().y - screen.min().y);
|
|
float zoom = std::min(zoomV, zoomH);
|
|
|
|
float offH = screen.center().x;
|
|
float offV = screen.center().y;
|
|
|
|
Matrix4 crop = Matrix4::identity();
|
|
|
|
crop[0][0] = zoom;
|
|
crop[1][1] = zoom;
|
|
crop[0][3] = -zoom * offH;
|
|
crop[1][3] = -zoom * offV;
|
|
|
|
visualEngine->getCameraMutable().setProjectionMatrix( crop * visualEngine->getCamera().getProjectionMatrix() );
|
|
}
|
|
|
|
void RenderView::prepareSceneGraph()
|
|
{
|
|
// will populate scenegraph, and trigger resource loads.
|
|
visualEngine->getSceneUpdater()->setComputeLightingEnabled(false);
|
|
|
|
// wait for all networked resources to load.
|
|
// do this outside scoped lock because this is the IO/network bound task.
|
|
|
|
RBX::ContentProvider* contentProvider = visualEngine->getContentProvider();
|
|
RBX::MeshContentProvider* meshContentProvider = visualEngine->getMeshContentProvider();
|
|
bool allContentLoaded = false;
|
|
|
|
do
|
|
{
|
|
FASTLOG(FLog::ThumbnailRender, "Waiting for content to load");
|
|
|
|
waitForContent(contentProvider);
|
|
|
|
RBX::ServiceProvider::find<RBX::Workspace>(dataModel.get())->assemble();
|
|
|
|
renderPrepareImpl(NULL, /* updateViewport= */ false);
|
|
|
|
// Clear adorn rendering queue
|
|
visualEngine->getAdorn()->finishRenderPass();
|
|
|
|
// Load textures
|
|
while (!visualEngine->getTextureManager()->isQueueEmpty())
|
|
{
|
|
visualEngine->getTextureManager()->processPendingRequests();
|
|
|
|
boost::this_thread::sleep(boost::posix_time::milliseconds(20));
|
|
}
|
|
|
|
// Run texture compositor
|
|
if (!visualEngine->getTextureCompositor()->isQueueEmpty())
|
|
{
|
|
if (DeviceContext* context = visualEngine->getDevice()->beginFrame())
|
|
{
|
|
visualEngine->getTextureCompositor()->render(context);
|
|
visualEngine->getDevice()->endFrame();
|
|
}
|
|
}
|
|
|
|
allContentLoaded =
|
|
contentProvider->isRequestQueueEmpty() &&
|
|
meshContentProvider->isRequestQueueEmpty() &&
|
|
!visualEngine->getSceneUpdater()->arePartsWaitingForAssets() &&
|
|
visualEngine->getTextureCompositor()->isQueueEmpty() &&
|
|
visualEngine->getTextureManager()->isQueueEmpty();
|
|
|
|
FASTLOG2(FLog::ThumbnailRender, "After render: Providers requests empty. Content: %u, Mesh: %u",
|
|
contentProvider->isRequestQueueEmpty(), meshContentProvider->isRequestQueueEmpty());
|
|
|
|
FASTLOG3(FLog::ThumbnailRender, "Parts not waiting for assets: %u, Texture Compositor Queue Empty: %u, Texture Manager Queue Empty: %u",
|
|
!visualEngine->getSceneUpdater()->arePartsWaitingForAssets(), visualEngine->getTextureCompositor()->isQueueEmpty(), visualEngine->getTextureManager()->isQueueEmpty());
|
|
} while(!allContentLoaded);
|
|
|
|
visualEngine->getSceneUpdater()->setComputeLightingEnabled(true);
|
|
}
|
|
|
|
void RenderView::renderThumb(unsigned char* data, int width, int height, bool crop, bool allowDolly)
|
|
{
|
|
// will populate scenegraph, and trigger resource loads.
|
|
FASTLOG(FLog::ThumbnailRender, "Rendering thumbnail: populating scene graph, trigger resource load");
|
|
prepareSceneGraph();
|
|
|
|
visualEngine->getFrameRateManager()->SubmitCurrentFrame( 1000, 1000, 1000, 0 );
|
|
|
|
// Run find visible objects once to fill in distance for LightGrid
|
|
visualEngine->getSceneManager()->computeMinimumSqDistance(visualEngine->getCamera());
|
|
|
|
visualEngine->setViewport(width, height);
|
|
|
|
if (Camera* camera = dataModel->getWorkspace()->getCamera())
|
|
camera->setViewport(Vector2int16(width, height));
|
|
|
|
renderPrepareImpl(NULL, /* updateViewport= */ false);
|
|
|
|
if (crop)
|
|
{
|
|
modifyThumbnailCamera(visualEngine.get(),allowDolly);
|
|
}
|
|
|
|
shared_ptr<Renderbuffer> color = visualEngine->getDevice()->createRenderbuffer(Texture::Format_RGBA8, width, height, 1);
|
|
shared_ptr<Renderbuffer> depth = visualEngine->getDevice()->createRenderbuffer(Texture::Format_D24S8, width, height, 1);
|
|
shared_ptr<Framebuffer> framebuffer = visualEngine->getDevice()->createFramebuffer(color, depth);
|
|
|
|
renderPerformImpl(0.f, framebuffer.get());
|
|
|
|
framebuffer->download(data, width * height * 4);
|
|
}
|
|
|
|
//
|
|
// Write render target to file, using user picture folder\Roblox
|
|
//
|
|
bool RenderView::saveScreenshotToFile(std::string& /*output*/ filename)
|
|
{
|
|
#if defined(RBX_PLATFORM_DURANGO)
|
|
// TODO: implement screenshot for durango
|
|
#else
|
|
Framebuffer* framebuffer = visualEngine->getDevice()->getMainFramebuffer();
|
|
|
|
if (framebuffer)
|
|
{
|
|
time_t ctTime;
|
|
time(&ctTime);
|
|
|
|
struct tm* pTime = localtime(&ctTime);
|
|
|
|
std::ostringstream name;
|
|
name << "RobloxScreenShot";
|
|
name << std::setw(2) << std::setfill('0') << (pTime->tm_mon + 1)
|
|
<< std::setw(2) << std::setfill('0') << pTime->tm_mday
|
|
<< std::setw(2) << std::setfill('0') << (pTime->tm_year + 1900)
|
|
<< "_" << std::setw(2) << std::setfill('0') << pTime->tm_hour
|
|
<< std::setw(2) << std::setfill('0') << pTime->tm_min
|
|
<< std::setw(2) << std::setfill('0') << pTime->tm_sec
|
|
<< std::setw(3) << std::setfill('0') << ((clock() * 1000 / CLOCKS_PER_SEC) % 1000);
|
|
name << ".png";
|
|
|
|
boost::filesystem::path path = FileSystem::getUserDirectory(true, RBX::DirPicture) / name.str();
|
|
|
|
G3D::GImage image(framebuffer->getWidth(), framebuffer->getHeight(), 4);
|
|
framebuffer->download(image.byte(), image.width() * image.height() * 4);
|
|
|
|
image.convertToRGB();
|
|
|
|
G3D::BinaryOutput binaryOutput;
|
|
image.encode(G3D::GImage::PNG, binaryOutput);
|
|
|
|
std::ofstream out(path.native().c_str(), std::ios::out | std::ios::binary);
|
|
out.write(reinterpret_cast<const char*>(binaryOutput.getCArray()), binaryOutput.length());
|
|
|
|
// Not Unicode-safe! :(
|
|
filename = path.string();
|
|
|
|
return true;
|
|
}
|
|
#endif
|
|
return false;
|
|
}
|
|
|
|
void RenderView::queueAssetReload(const std::string& filePath)
|
|
{
|
|
visualEngine->queueAssetReload(filePath);
|
|
}
|
|
|
|
void RenderView::immediateAssetReload(const std::string& filePath)
|
|
{
|
|
visualEngine->immediateAssetReload(filePath);
|
|
}
|
|
|
|
bool RenderView::exportSceneThumbJSON(ExporterSaveType saveType, ExporterFormat format, bool encodeBase64, std::string& strOut)
|
|
{
|
|
#if !defined(RBX_PLATFORM_DURANGO)
|
|
FASTLOG(FLog::ThumbnailRender, "Rendering thumbnail: populating scene graph, trigger resource load");
|
|
prepareSceneGraph();
|
|
|
|
return ObjectExporter::exportToJSON(saveType, format, dataModel.get(), visualEngine.get(), encodeBase64, &strOut);
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
bool RenderView::exportScene(const std::string& filePath, ExporterSaveType saveType, ExporterFormat format)
|
|
{
|
|
#if !defined(RBX_PLATFORM_DURANGO)
|
|
return ObjectExporter::exportToFile(filePath, saveType, format, dataModel.get(), visualEngine.get());
|
|
#else
|
|
return false;
|
|
#endif
|
|
}
|
|
|
|
|
|
void RenderView::suspendView()
|
|
{
|
|
device->suspend();
|
|
}
|
|
|
|
void RenderView::resumeView()
|
|
{
|
|
device->resume();
|
|
}
|
|
|
|
void RenderView::garbageCollect()
|
|
{
|
|
visualEngine->getMaterialGenerator()->garbageCollectFull();
|
|
visualEngine->getTextureCompositor()->garbageCollectFull();
|
|
visualEngine->getTextureManager()->garbageCollectFull();
|
|
|
|
if (RBX::ContentProvider* contentProvider = visualEngine->getContentProvider())
|
|
{
|
|
contentProvider->clearContent();
|
|
}
|
|
|
|
if (RBX::MeshContentProvider* meshContentProvider = visualEngine->getMeshContentProvider())
|
|
{
|
|
meshContentProvider->clearContent();
|
|
}
|
|
}
|
|
|
|
std::pair<unsigned, unsigned> RenderView::setFrameDataCallback(const boost::function<void(void*)>& callback)
|
|
{
|
|
Framebuffer* framebuffer = visualEngine->getDevice()->getMainFramebuffer();
|
|
if (callback && framebuffer)
|
|
{
|
|
videoFrameVertexStreamer.reset(new VertexStreamer(visualEngine.get()));
|
|
frameDataCallback = callback;
|
|
return std::make_pair(framebuffer->getWidth(), framebuffer->getHeight());
|
|
}
|
|
else
|
|
{
|
|
videoFrameVertexStreamer.reset();
|
|
frameDataCallback = FrameDataCallback();
|
|
return std::make_pair(0, 0);
|
|
}
|
|
}
|
|
|
|
double busyWaitLoop(double ms)
|
|
{
|
|
double start = Time::now(Time::Precise).timestampSeconds() * 1000;
|
|
for (;;)
|
|
{
|
|
double cur = Time::now(Time::Precise).timestampSeconds() * 1000;
|
|
if (cur-start > ms)
|
|
return cur-start;
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|